Spaces:
Running
Running
mude o botão Professor para em baixo no iniciar questionário - Follow Up Deployment
52ef7be
verified
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Questionário sobre Cidade Cinza</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Custom CSS for shuffle animation */ | |
| .shuffle-animation { | |
| animation: shuffle 0.5s ease; | |
| } | |
| @keyframes shuffle { | |
| 0% { transform: translateY(0); } | |
| 25% { transform: translateY(-10px); } | |
| 50% { transform: translateY(0); } | |
| 75% { transform: translateY(10px); } | |
| 100% { transform: translateY(0); } | |
| } | |
| /* Results table styling */ | |
| .results-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| .results-table th, .results-table td { | |
| border: 1px solid #ddd; | |
| padding: 8px; | |
| } | |
| .results-table tr:nth-child(even) { | |
| background-color: #f2f2f2; | |
| } | |
| .results-table tr:hover { | |
| background-color: #ddd; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <!-- Teacher Login Screen --> | |
| <div id="teacherLoginScreen" class="min-h-screen hidden items-center justify-center px-4 py-12"> | |
| <div class="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden"> | |
| <div class="bg-purple-600 py-4 px-6"> | |
| <h2 class="text-2xl font-bold text-white">Acesso do Professor</h2> | |
| <p class="text-purple-100 mt-1">Insira suas credenciais para acessar os resultados</p> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-4"> | |
| <label for="teacherUsername" class="block text-gray-700 font-medium mb-2">Usuário</label> | |
| <input type="text" id="teacherUsername" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Seu usuário" required> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="teacherPassword" class="block text-gray-700 font-medium mb-2">Senha</label> | |
| <input type="password" id="teacherPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Sua senha" required> | |
| </div> | |
| <button id="teacherLoginBtn" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition duration-150"> | |
| Acessar Painel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Teacher Password Modal --> | |
| <div id="teacherPasswordModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
| <div class="bg-white rounded-lg shadow-md overflow-hidden w-96"> | |
| <div class="bg-purple-600 py-3 px-6"> | |
| <h3 class="text-lg font-bold text-white">Acesso Restrito</h3> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-4"> | |
| <label for="teacherPasswordInput" class="block text-gray-700 mb-2">Senha do Professor:</label> | |
| <input type="password" id="teacherPasswordInput" class="w-full px-3 py-2 border rounded-md"> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button id="cancelPasswordBtn" class="px-4 py-2 bg-gray-300 rounded-md">Cancelar</button> | |
| <button id="confirmPasswordBtn" class="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">Acessar</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Login Screen --> | |
| <div id="loginScreen" class="min-h-screen flex items-center justify-center px-4 py-12"> | |
| <div class="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden"> | |
| <div class="bg-blue-600 py-4 px-6"> | |
| <h2 class="text-2xl font-bold text-white">Questionário sobre "Cidade Cinza"</h2> | |
| <p class="text-blue-100 mt-1">Por favor, insira suas informações para começar</p> | |
| </div> | |
| <div class="px-6 pb-4"> | |
| <button id="teacherAccessBtn" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-150"> | |
| Professor | |
| </button> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-4"> | |
| <label for="studentName" class="block text-gray-700 font-medium mb-2">Nome do Aluno</label> | |
| <input type="text" id="studentName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Seu nome completo" required> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="studentClass" class="block text-gray-700 font-medium mb-2">Turma</label> | |
| <select id="studentClass" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required> | |
| <option value="">Selecione sua turma</option> | |
| <option value="1n1">1n1</option> | |
| <option value="1n2">1n2</option> | |
| <option value="2n1">2n1</option> | |
| <option value="2n2">2n2</option> | |
| <option value="3n1">3n1</option> | |
| <option value="3n2">3n2</option> | |
| <option value="1eja">1eja</option> | |
| <option value="2eja">2eja</option> | |
| <option value="3eja">3eja</option> | |
| </select> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="quizDate" class="block text-gray-700 font-medium mb-2">Data</label> | |
| <input type="date" id="quizDate" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required> | |
| </div> | |
| <button id="startQuizBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-150"> | |
| Iniciar Questionário | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quiz Screen --> | |
| <div id="quizScreen" class="min-h-screen hidden py-8 px-4"> | |
| <div class="max-w-4xl mx-auto"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h1 class="text-2xl font-bold text-gray-800">Questionário sobre "Cidade Cinza"</h1> | |
| <div class="bg-gray-200 rounded-full px-4 py-2"> | |
| <span id="timer" class="text-gray-700 font-medium">00:00</span> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-md overflow-hidden mb-6"> | |
| <div class="bg-gray-800 text-white px-4 py-3 flex justify-between items-center"> | |
| <h2 id="questionNumber" class="text-lg font-semibold">Pergunta 1</h2> | |
| <div class="text-sm"> | |
| <span id="currentQuestion">1</span> de <span id="totalQuestions">10</span> | |
| </div> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-6 p-4 bg-gray-100 rounded-lg"> | |
| <p id="questionText" class="text-lg font-medium text-gray-800">A pergunta aparecerá aqui...</p> | |
| </div> | |
| <div id="imageContainer" class="mb-6 flex justify-center"> | |
| <img id="questionImage" src="" alt="Imagem ilustrativa" class="max-h-60 rounded-lg shadow-md"> | |
| </div> | |
| <div id="answersContainer" class="space-y-3"> | |
| <!-- Answers will be inserted here --> | |
| </div> | |
| <div class="mt-8 flex justify-between"> | |
| <button id="prevQuestionBtn" class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition duration-150"> | |
| <i class="fas fa-arrow-left"></i> Anterior | |
| </button> | |
| <button id="nextQuestionBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-150"> | |
| Próxima <i class="fas fa-arrow-right"></i> | |
| </button> | |
| <button id="submitQuizBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition duration-150 hidden"> | |
| Finalizar <i class="fas fa-check"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-md p-4"> | |
| <div class="flex flex-wrap gap-2"> | |
| <!-- Question navigation buttons will be inserted here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Results Screen --> | |
| <div id="resultsScreen" class="min-h-screen hidden py-8 px-4"> | |
| <div class="max-w-3xl mx-auto"> | |
| <div class="bg-white rounded-lg shadow-md overflow-hidden"> | |
| <div class="bg-green-600 py-4 px-6"> | |
| <h2 class="text-2xl font-bold text-white">Resultado do Questionário</h2> | |
| </div> | |
| <div class="p-6"> | |
| <div class="flex items-center mb-6"> | |
| <div class="h-24 w-24 bg-blue-100 rounded-full flex items-center justify-center mr-6"> | |
| <span id="scoreCircle" class="text-3xl font-bold text-blue-600">0</span> | |
| </div> | |
| <div> | |
| <h3 class="text-xl font-semibold text-gray-800" id="studentNameDisplay"></h3> | |
| <p class="text-gray-600" id="studentInfoDisplay"></p> | |
| <p class="mt-2 text-gray-800"><span id="correctAnswers">0</span> de <span id="totalQuestionsDisplay">10</span> corretas (<span id="percentage">0</span>%)</p> | |
| </div> | |
| </div> | |
| <div id="wrongAnswersContainer" class="mb-6"> | |
| <!-- Wrong answers will be inserted here --> | |
| </div> | |
| <div class="flex justify-between"> | |
| <button id="viewResultsBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-150"> | |
| Ver Resultados Detalhados | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Admin Panel (hidden by default, accessible via URL parameter) --> | |
| <div id="adminPanel" class="min-h-screen hidden py-8 px-4"> | |
| <div class="max-w-6xl mx-auto"> | |
| <div class="bg-white rounded-lg shadow-md overflow-hidden"> | |
| <div class="bg-purple-600 py-4 px-6"> | |
| <h2 class="text-2xl font-bold text-white">Painel Administrativo</h2> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-6 flex justify-between items-center"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800">Resultados dos Alunos</h3> | |
| <p class="text-gray-600">Visualize todas as respostas e desempenhos</p> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <select id="classFilter" class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"> | |
| <option value="">Todas as Turmas</option> | |
| <option value="1n1">1n1</option> | |
| <option value="1n2">1n2</option> | |
| <option value="2n1">2n1</option> | |
| <option value="2n2">2n2</option> | |
| <option value="3n1">3n1</option> | |
| <option value="3n2">3n2</option> | |
| <option value="1eja">1eja</option> | |
| <option value="2eja">2eja</option> | |
| <option value="3eja">3eja</option> | |
| </select> | |
| <button id="exportResultsBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition duration-150"> | |
| Exportar Dados | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="classFilter" class="block text-gray-700 font-medium mb-2">Filtrar por Turma:</label> | |
| <select id="classFilter" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"> | |
| <option value="">Todas as turmas</option> | |
| <option value="1n1">1n1</option> | |
| <option value="1n2">1n2</option> | |
| <option value="2n1">2n1</option> | |
| <option value="2n2">2n2</option> | |
| <option value="3n1">3n1</option> | |
| <option value="3n2">3n2</option> | |
| <option value="1eja">1eja</option> | |
| <option value="2eja">2eja</option> | |
| <option value="3eja">3eja</option> | |
| </select> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table id="resultsTable" class="results-table w-full"> | |
| <thead> | |
| <tr class="bg-gray-100"> | |
| <th class="text-left py-2 px-4">Aluno</th> | |
| <th class="text-left py-2 px-4">Turma</th> | |
| <th class="text-left py-2 px-4">Data</th> | |
| <th class="text-left py-2 px-4">Acertos</th> | |
| <th class="text-left py-2 px-4">Nota (0-10)</th> | |
| <th class="text-left py-2 px-4">% de Acerto</th> | |
| <th class="text-left py-2 px-4">Tempo</th> | |
| </tr> | |
| </thead> | |
| <tbody id="resultsTableBody"> | |
| <!-- Results will be inserted here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="mt-4 bg-gray-100 p-4 rounded-lg"> | |
| <h3 class="font-medium text-gray-800 mb-2">Estatísticas por Turma:</h3> | |
| <div id="classStats" class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <!-- Class statistics will be inserted here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Question data with images (using placeholder images from Unsplash) | |
| const questions = [ | |
| { | |
| question: "Qual é a principal diferença entre grafite e pixo, segundo os artistas entrevistados no documentário \"Cidade Cinza\"?", | |
| answers: [ | |
| { text: "O grafite é sempre feito com permissão enquanto o pixo é sempre ilegal", correct: false }, | |
| { text: "O grafite é considerado arte enquanto o pixo é visto apenas como vandalismo puro", correct: false }, | |
| { text: "O grafite prioriza a estética e o apelo visual, já o pixo enfatiza a mensagem e a ocupação do espaço urbano", correct: true }, | |
| { text: "O grafite utiliza apenas tintas spray profissionais enquanto o pixo usa materiais improvisados", correct: false }, | |
| { text: "Não existe diferença significativa entre as duas manifestações urbanas", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?graffiti,art", | |
| explanation: "Resposta correta: O grafite prioriza a estética e o apelo visual, já o pixo enfatiza a mensagem e a ocupação do espaço urbano. O documentário mostra que grafiteiros buscam beleza plástica, enquanto pichadores veem seu ato como marca de presença e protesto." | |
| }, | |
| { | |
| question: "Como os artistas retratados no documentário justificam o pixo como forma de expressão artística?", | |
| answers: [ | |
| { text: "Por ser uma técnica mais simples e acessível do que o grafite tradicional", correct: false }, | |
| { text: "Por representar uma forma de contestação social e afirmação de presença no espaço público", correct: true }, | |
| { text: "Por ser mais facilmente aceito e compreendido pelo grande público", correct: false }, | |
| { text: "Por exigir menos habilidade técnica e preparo artístico", correct: false }, | |
| { text: "Por ser sempre realizado com autorização dos proprietários dos muros", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?street,art", | |
| explanation: "Resposta correta: Por representar uma forma de contestação social e afirmação de presença no espaço público. Muitos pichadores veem seu ato como resistência ao controle do espaço urbano." | |
| }, | |
| { | |
| question: "Qual foi o papel dos irmãos Os Gêmeos no desenvolvimento do grafite em São Paulo, conforme apresentado no documentário?", | |
| answers: [ | |
| { text: "Foram os pioneiros absolutos na introdução do pixo na cidade", correct: false }, | |
| { text: "Contribuíram para internacionalizar o grafite brasileiro, dando visibilidade global à cena paulistana", correct: true }, | |
| { text: "Atuaram como críticos ferrenhos de qualquer forma de arte urbana", correct: false }, | |
| { text: "Trabalharam exclusivamente em espaços institucionais, nunca em intervenções urbanas", correct: false }, | |
| { text: "Foram responsáveis diretos pela criminalização do grafite na cidade", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?osgemeos,art", | |
| explanation: "Resposta correta: Contribuíram para internacionalizar o grafite brasileiro, dando visibilidade global à cena paulistana. Os Gêmeos foram fundamentais para colocar o grafite brasileiro no cenário mundial." | |
| }, | |
| { | |
| question: "Como \"Cidade Cinza\" retrata a relação entre os artistas urbanos e o poder público municipal?", | |
| answers: [ | |
| { text: "Mostra uma cooperação constante e harmoniosa entre as partes", correct: false }, | |
| { text: "Revela um cenário de conflito com períodos de repressão, mas também momentos de reconhecimento oficial", correct: true }, | |
| { text: "Apresenta uma completa ausência de interferência governamental", correct: false }, | |
| { text: "Demonstra que todos os artistas foram contratados formalmente pela prefeitura", correct: false }, | |
| { text: "Indica que a prefeitura apoiava apenas o pixo, nunca o grafite", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?city,government", | |
| explanation: "Resposta correta: Revela um cenário de conflito com períodos de repressão, mas também momentos de reconhecimento oficial. O documentário mostra essa relação ambígua, com repressão e também projetos de urbanismo que incorporam o grafite." | |
| }, | |
| { | |
| question: "Qual a posição predominante entre os artistas do filme em relação à criminalização do pixo?", | |
| answers: [ | |
| { text: "Concordam plenamente com sua classificação como ato vandalico", correct: false }, | |
| { text: "Defendem sua legitimidade como expressão cultural e criticam a repressão sistemática", correct: true }, | |
| { text: "Acreditam que apenas o grafite deveria ser permitido por lei", correct: false }, | |
| { text: "Consideram que o pixo só é aceitável em áreas abandonadas", correct: false }, | |
| { text: "Não manifestam qualquer opinião formada sobre o assunto", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?protest,art", | |
| explanation: "Resposta correta: Defendem sua legitimidade como expressão cultural e criticam a repressão sistemática. Pichadores veem a criminalização como uma tentativa de silenciar formas de expressão marginalizadas." | |
| }, | |
| { | |
| question: "Qual foi o impacto da Lei Cidade Limpa sobre as manifestações de arte urbana em São Paulo, segundo o documentário?", | |
| answers: [ | |
| { text: "Estimulou o surgimento de novos artistas de rua", correct: false }, | |
| { text: "Intensificou a fiscalização e repressão contra qualquer tipo de intervenção urbana", correct: true }, | |
| { text: "Afetou apenas a publicidade comercial, sem relação com a arte de rua", correct: false }, | |
| { text: "Transformou o pixo em patrimônio cultural oficial", correct: false }, | |
| { text: "Na prática, nunca foi implementada de fato", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?law,sign", | |
| explanation: "Resposta correta: Intensificou a fiscalização e repressão contra qualquer tipo de intervenção urbana. A lei, originalmente voltada contra a poluição visual, foi aplicada também contra expressões artísticas." | |
| }, | |
| { | |
| question: "Como os artistas do filme percebem a inserção do grafite no circuito comercial de arte?", | |
| answers: [ | |
| { text: "Consideram que isso representa a perda da essência contestadora original", correct: true }, | |
| { text: "Veem como a única forma legítima de valorização do grafite", correct: false }, | |
| { text: "Acreditam que o mercado nunca realmente aceitou o grafite como arte", correct: false }, | |
| { text: "Não fazem distinção entre grafite e pixo no contexto mercadológico", correct: false }, | |
| { text: "Avaliam que o pixo tem maior valor comercial que o grafite", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?commerce,art", | |
| explanation: "Resposta correta: Consideram que isso representa a perda da essência contestadora original. Alguns veem a comercialização como traição aos princípios de resistência presente na arte de rua." | |
| }, | |
| { | |
| question: "O que o documentário revela sobre as tensões entre grafiteiros e pichadores?", | |
| answers: [ | |
| { text: "Mostra uma convivência sempre pacífica e colaborativa", correct: false }, | |
| { text: "Expõe rivalidades, com grafiteiros muitas vezes criticando o pixo como vandalismo e pichadores acusando grafiteiros de \"vendidos\"", correct: true }, | |
| { text: "Indica que todos os grafiteiros também praticam pixo regularmente", correct: false }, | |
| { text: "Demonstra completa ausência de conflitos entre os grupos", correct: false }, | |
| { text: "Revela que a polícia sempre mediou positivamente essas relações", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?conflict,street", | |
| explanation: "Resposta correta: Expõe rivalidades, com grafiteiros muitas vezes criticando o pixo como vandalismo e pichadores acusando grafiteiros de \"vendidos\". Essa divisão reflete diferentes visões sobre o papel da arte urbana." | |
| }, | |
| { | |
| question: "Qual é a visão central apresentada pelo documentário sobre o papel da arte urbana na transformação das cidades?", | |
| answers: [ | |
| { text: "Que ela contribui para piorar a degradação dos centros urbanos", correct: false }, | |
| { text: "Que grafite e pixo funcionam como formas de resistência e humanização dos espaços cinzentos", correct: true }, | |
| { text: "Que deveria ser restrita a museus e galerias especializadas", correct: false }, | |
| { text: "Que não possui qualquer capacidade transformadora real", correct: false }, | |
| { text: "Que apenas o grafite tem este poder, nunca o pixo", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?city,transformation", | |
| explanation: "Resposta correta: Que grafite e pixo funcionam como formas de resistência e humanização dos espaços cinzentos. O filme mostra como essas expressões trazem vida e crítica à paisagem urbana." | |
| }, | |
| { | |
| question: "Como os artistas entrevistados em \"Cidade Cinza\" enxergam o futuro dessas manifestações artísticas urbanas?", | |
| answers: [ | |
| { text: "Acreditam que ambas estão fadadas ao desaparecimento devido à repressão crescente", correct: false }, | |
| { text: "Preveem que o grafite se tornará a única forma aceita de arte de rua", correct: false }, | |
| { text: "Percebem que continuarão evoluindo e se adaptando como formas de expressão e resistência", correct: true }, | |
| { text: "Esperam que o pixo substitua completamente o grafite no cenário urbano", correct: false }, | |
| { text: "Não demonstram qualquer preocupação ou reflexão sobre o futuro", correct: false } | |
| ], | |
| image: "https://source.unsplash.com/random/600x400/?future,city", | |
| explanation: "Resposta correta: Percebem que continuarão evoluindo e se adaptando como formas de expressão e resistência. Apesar da repressão, os artistas veem essas manifestações como persistentes e mutáveis." | |
| } | |
| ]; | |
| // DOM Elements | |
| const teacherLoginScreen = document.getElementById('teacherLoginScreen'); | |
| const teacherUsername = document.getElementById('teacherUsername'); | |
| const teacherPassword = document.getElementById('teacherPassword'); | |
| const teacherLoginBtn = document.getElementById('teacherLoginBtn'); | |
| const loginScreen = document.getElementById('loginScreen'); | |
| const quizScreen = document.getElementById('quizScreen'); | |
| const resultsScreen = document.getElementById('resultsScreen'); | |
| const adminPanel = document.getElementById('adminPanel'); | |
| const studentName = document.getElementById('studentName'); | |
| const studentClass = document.getElementById('studentClass'); | |
| const quizDate = document.getElementById('quizDate'); | |
| const startQuizBtn = document.getElementById('startQuizBtn'); | |
| const questionText = document.getElementById('questionText'); | |
| const questionImage = document.getElementById('questionImage'); | |
| const answersContainer = document.getElementById('answersContainer'); | |
| const prevQuestionBtn = document.getElementById('prevQuestionBtn'); | |
| const nextQuestionBtn = document.getElementById('nextQuestionBtn'); | |
| const submitQuizBtn = document.getElementById('submitQuizBtn'); | |
| const questionNumber = document.getElementById('questionNumber'); | |
| const currentQuestion = document.getElementById('currentQuestion'); | |
| const totalQuestions = document.getElementById('totalQuestions'); | |
| const timer = document.getElementById('timer'); | |
| const studentNameDisplay = document.getElementById('studentNameDisplay'); | |
| const studentInfoDisplay = document.getElementById('studentInfoDisplay'); | |
| const correctAnswers = document.getElementById('correctAnswers'); | |
| const totalQuestionsDisplay = document.getElementById('totalQuestionsDisplay'); | |
| const percentage = document.getElementById('percentage'); | |
| const scoreCircle = document.getElementById('scoreCircle'); | |
| const wrongAnswersContainer = document.getElementById('wrongAnswersContainer'); | |
| const viewResultsBtn = document.getElementById('viewResultsBtn'); | |
| const resultsTableBody = document.getElementById('resultsTableBody'); | |
| const classFilter = document.getElementById('classFilter'); | |
| const exportResultsBtn = document.getElementById('exportResultsBtn'); | |
| // Quiz State | |
| let quizState = { | |
| currentQuestionIndex: 0, | |
| shuffledQuestions: [], | |
| shuffledAnswers: [], | |
| userAnswers: Array(questions.length).fill(null), | |
| timeElapsed: 0, | |
| timerInterval: null, | |
| studentName: '', | |
| studentClass: '', | |
| quizDate: '', | |
| isFinished: false | |
| }; | |
| // Admin mode check from URL | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const adminMode = urlParams.get('admin'); | |
| // Teacher credentials (in a real app, these would be server-side) | |
| const TEACHER_CREDENTIALS = { | |
| username: "professor", | |
| password: "escola123" | |
| }; | |
| // Initialize the app | |
| function initApp() { | |
| // Set up teacher password modal | |
| const teacherPasswordModal = document.getElementById('teacherPasswordModal'); | |
| const teacherAccessBtn = document.getElementById('teacherAccessBtn'); | |
| const teacherPasswordInput = document.getElementById('teacherPasswordInput'); | |
| const cancelPasswordBtn = document.getElementById('cancelPasswordBtn'); | |
| const confirmPasswordBtn = document.getElementById('confirmPasswordBtn'); | |
| teacherAccessBtn.addEventListener('click', () => { | |
| teacherPasswordModal.classList.remove('hidden'); | |
| }); | |
| cancelPasswordBtn.addEventListener('click', () => { | |
| teacherPasswordModal.classList.add('hidden'); | |
| teacherPasswordInput.value = ''; | |
| }); | |
| confirmPasswordBtn.addEventListener('click', () => { | |
| if (teacherPasswordInput.value === 'Profandre123') { | |
| teacherPasswordModal.classList.add('hidden'); | |
| loginScreen.classList.add('hidden'); | |
| adminPanel.classList.remove('hidden'); | |
| loadResults(); | |
| } else { | |
| alert('Senha incorreta!'); | |
| } | |
| teacherPasswordInput.value = ''; | |
| }); | |
| // Allow pressing Enter in password field | |
| teacherPasswordInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| confirmPasswordBtn.click(); | |
| } | |
| }); | |
| // Set up teacher login button | |
| if (teacherLoginBtn) { | |
| teacherLoginBtn.addEventListener('click', () => { | |
| if (teacherUsername.value === TEACHER_CREDENTIALS.username && | |
| teacherPassword.value === TEACHER_CREDENTIALS.password) { | |
| teacherLoginScreen.classList.add('hidden'); | |
| adminPanel.classList.remove('hidden'); | |
| loadResults(); | |
| } else { | |
| alert('Credenciais inválidas. Tente novamente.'); | |
| } | |
| }); | |
| } | |
| if (adminMode === 'true') { | |
| loginScreen.classList.add('hidden'); | |
| quizScreen.classList.add('hidden'); | |
| resultsScreen.classList.add('hidden'); | |
| adminPanel.classList.remove('hidden'); | |
| loadResults(); | |
| return; | |
| } | |
| // Set up event listeners | |
| startQuizBtn.addEventListener('click', startQuiz); | |
| prevQuestionBtn.addEventListener('click', showPreviousQuestion); | |
| nextQuestionBtn.addEventListener('click', showNextQuestion); | |
| submitQuizBtn.addEventListener('click', showResults); | |
| viewResultsBtn.addEventListener('click', showDetailedResults); | |
| classFilter.addEventListener('change', filterResults); | |
| exportResultsBtn.addEventListener('click', exportResults); | |
| // Set today's date as default | |
| const today = new Date().toISOString().split('T')[0]; | |
| quizDate.value = today; | |
| // Initialize the question navigator | |
| initQuestionNavigator(); | |
| // Display total questions | |
| totalQuestions.textContent = questions.length; | |
| totalQuestionsDisplay.textContent = questions.length; | |
| } | |
| // Initialize question navigator buttons | |
| function initQuestionNavigator() { | |
| const questionNav = document.querySelector('.flex.flex-wrap.gap-2'); | |
| questionNav.innerHTML = ''; | |
| questions.forEach((_, index) => { | |
| const btn = document.createElement('button'); | |
| btn.className = `w-10 h-10 rounded-full flex items-center justify-center ${index === 0 ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700 hover:bg-gray-300'} transition`; | |
| btn.textContent = index + 1; | |
| btn.onclick = () => goToQuestion(index); | |
| const btnContainer = document.createElement('div'); | |
| btnContainer.className = 'tooltip-container relative'; | |
| const tooltip = document.createElement('span'); | |
| tooltip.className = 'tooltip hidden absolute z-10 w-36 px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm dark:bg-gray-700 bottom-full mb-2 left-1/2 transform -translate-x-1/2'; | |
| tooltip.textContent = `Questão ${index + 1}`; | |
| btnContainer.appendChild(btn); | |
| btnContainer.appendChild(tooltip); | |
| // Show tooltip on hover | |
| btn.addEventListener('mouseover', () => { | |
| tooltip.classList.remove('hidden'); | |
| }); | |
| btn.addEventListener('mouseout', () => { | |
| tooltip.classList.add('hidden'); | |
| }); | |
| questionNav.appendChild(btnContainer); | |
| }); | |
| } | |
| // Start the quiz | |
| function startQuiz() { | |
| // Check if a quiz was already completed | |
| if (localStorage.getItem('quizCompleted_' + studentName.value)) { | |
| alert('Você já completou este questionário e não pode realizá-lo novamente.'); | |
| return; | |
| } | |
| if (!studentName.value || !studentClass.value || !quizDate.value) { | |
| alert('Por favor, preencha todas as informações antes de começar.'); | |
| return; | |
| } | |
| // Save student info | |
| quizState.studentName = studentName.value; | |
| quizState.studentClass = studentClass.value; | |
| quizState.quizDate = quizDate.value; | |
| // Shuffle questions and answers | |
| quizState.shuffledQuestions = shuffleArray([...questions]); | |
| quizState.shuffledAnswers = quizState.shuffledQuestions.map(q => shuffleArray([...q.answers])); | |
| quizState.userAnswers = Array(questions.length).fill(null); | |
| quizState.currentQuestionIndex = 0; | |
| quizState.timeElapsed = 0; | |
| // Start timer | |
| startTimer(); | |
| // Hide login screen and show quiz screen | |
| loginScreen.classList.add('hidden'); | |
| quizScreen.classList.remove('hidden'); | |
| // Display first question | |
| displayQuestion(); | |
| } | |
| // Display current question | |
| function displayQuestion() { | |
| const currentQuestion = quizState.shuffledQuestions[quizState.currentQuestionIndex]; | |
| const currentAnswers = quizState.shuffledAnswers[quizState.currentQuestionIndex]; | |
| // Update question number | |
| questionNumber.textContent = `Pergunta ${quizState.currentQuestionIndex + 1}`; | |
| document.getElementById('currentQuestion').textContent = quizState.currentQuestionIndex + 1; | |
| // Update question text | |
| questionText.textContent = currentQuestion.question; | |
| // Update question image | |
| questionImage.src = currentQuestion.image; | |
| questionImage.alt = `Imagem ilustrativa para a pergunta ${quizState.currentQuestionIndex + 1}`; | |
| // Clear previous answers | |
| answersContainer.innerHTML = ''; | |
| // Add new answers | |
| currentAnswers.forEach((answer, index) => { | |
| const answerDiv = document.createElement('div'); | |
| answerDiv.className = 'flex items-center p-3 bg-gray-100 rounded-md cursor-pointer hover:bg-gray-200 transition'; | |
| answerDiv.dataset.index = index; | |
| const radio = document.createElement('input'); | |
| radio.type = 'radio'; | |
| radio.name = 'answer'; | |
| radio.id = `answer-${index}`; | |
| radio.className = 'mr-3 h-5 w-5 text-blue-600 focus:ring-blue-500'; | |
| // Check if this answer was previously selected | |
| if (quizState.userAnswers[quizState.currentQuestionIndex] === index) { | |
| radio.checked = true; | |
| } | |
| const label = document.createElement('label'); | |
| label.htmlFor = `answer-${index}`; | |
| label.className = 'text-gray-800 cursor-pointer'; | |
| label.textContent = answer.text; | |
| answerDiv.appendChild(radio); | |
| answerDiv.appendChild(label); | |
| // Add click event | |
| answerDiv.addEventListener('click', () => selectAnswer(index)); | |
| answersContainer.appendChild(answerDiv); | |
| }); | |
| // Enable/disable navigation buttons | |
| prevQuestionBtn.disabled = quizState.currentQuestionIndex === 0; | |
| nextQuestionBtn.classList.toggle('hidden', quizState.currentQuestionIndex === questions.length - 1); | |
| submitQuizBtn.classList.toggle('hidden', quizState.currentQuestionIndex !== questions.length - 1); | |
| // Update question navigator buttons | |
| updateQuestionNavigator(); | |
| } | |
| // Update question navigator buttons | |
| function updateQuestionNavigator() { | |
| const buttons = document.querySelectorAll('.flex.flex-wrap.gap-2 button'); | |
| buttons.forEach((btn, index) => { | |
| // Remove all active classes | |
| btn.classList.remove('bg-blue-600', 'text-white', 'bg-gray-200', 'text-gray-700', 'bg-green-100', 'text-green-800', 'bg-red-100', 'text-red-800'); | |
| // Current question | |
| if (index === quizState.currentQuestionIndex) { | |
| btn.classList.add('bg-blue-600', 'text-white'); | |
| } | |
| // Answered questions | |
| else if (quizState.userAnswers[index] !== null) { | |
| const isCorrect = checkAnswer(index, quizState.userAnswers[index]); | |
| btn.classList.add(isCorrect ? 'bg-green-100' : 'bg-red-100', isCorrect ? 'text-green-800' : 'text-red-800'); | |
| } | |
| // Unanswered questions | |
| else { | |
| btn.classList.add('bg-gray-200', 'text-gray-700'); | |
| } | |
| }); | |
| } | |
| // Select an answer | |
| function selectAnswer(answerIndex) { | |
| quizState.userAnswers[quizState.currentQuestionIndex] = answerIndex; | |
| updateQuestionNavigator(); | |
| } | |
| // Check if answer is correct | |
| function checkAnswer(questionIndex, answerIndex) { | |
| const question = quizState.shuffledQuestions[questionIndex]; | |
| const answer = quizState.shuffledAnswers[questionIndex][answerIndex]; | |
| return answer.correct; | |
| } | |
| // Navigate to previous question | |
| function showPreviousQuestion() { | |
| if (quizState.currentQuestionIndex > 0) { | |
| quizState.currentQuestionIndex--; | |
| displayQuestion(); | |
| } | |
| } | |
| // Navigate to next question | |
| function showNextQuestion() { | |
| if (quizState.userAnswers[quizState.currentQuestionIndex] === null) { | |
| alert('Por favor, selecione uma resposta antes de prosseguir.'); | |
| return; | |
| } | |
| if (quizState.currentQuestionIndex < questions.length - 1) { | |
| quizState.currentQuestionIndex++; | |
| displayQuestion(); | |
| } | |
| } | |
| // Navigate to specific question | |
| function goToQuestion(index) { | |
| quizState.currentQuestionIndex = index; | |
| displayQuestion(); | |
| } | |
| // Start timer | |
| function startTimer() { | |
| clearInterval(quizState.timerInterval); | |
| quizState.timeElapsed = 0; | |
| updateTimerDisplay(); | |
| quizState.timerInterval = setInterval(() => { | |
| quizState.timeElapsed++; | |
| updateTimerDisplay(); | |
| }, 1000); | |
| } | |
| // Update timer display | |
| function updateTimerDisplay() { | |
| const minutes = Math.floor(quizState.timeElapsed / 60).toString().padStart(2, '0'); | |
| const seconds = (quizState.timeElapsed % 60).toString().padStart(2, '0'); | |
| timer.textContent = `${minutes}:${seconds}`; | |
| } | |
| // Show results | |
| function showResults() { | |
| if (quizState.userAnswers[quizState.currentQuestionIndex] === null) { | |
| alert('Por favor, selecione uma resposta antes de finalizar.'); | |
| return; | |
| } | |
| // Stop timer | |
| clearInterval(quizState.timerInterval); | |
| // Calculate score | |
| const correctCount = quizState.userAnswers.reduce((count, answerIndex, questionIndex) => { | |
| return count + (checkAnswer(questionIndex, answerIndex) ? 1 : 0); | |
| }, 0); | |
| const score = Math.round((correctCount / questions.length) * 10); | |
| const percentage = (correctCount / questions.length * 100).toFixed(1); | |
| // Update results display | |
| studentNameDisplay.textContent = quizState.studentName; | |
| studentInfoDisplay.textContent = `${quizState.studentClass} - ${formatDate(quizState.quizDate)}`; | |
| correctAnswers.textContent = correctCount; | |
| percentage.textContent = percentage; | |
| scoreCircle.textContent = score; | |
| // Generate wrong answers list | |
| wrongAnswersContainer.innerHTML = ''; | |
| const wrongAnswersCount = questions.length - correctCount; | |
| if (wrongAnswersCount > 0) { | |
| const wrongAnswersTitle = document.createElement('h4'); | |
| wrongAnswersTitle.className = 'text-lg font-semibold mb-3 text-gray-800'; | |
| wrongAnswersTitle.textContent = `Correção das ${wrongAnswersCount === 1 ? 'resposta incorreta' : 'respostas incorretas'}:`; | |
| wrongAnswersContainer.appendChild(wrongAnswersTitle); | |
| quizState.userAnswers.forEach((userAnswer, index) => { | |
| if (!checkAnswer(index, userAnswer)) { | |
| const questionItem = document.createElement('div'); | |
| questionItem.className = 'mb-4 p-3 bg-red-50 rounded-md'; | |
| const questionText = document.createElement('p'); | |
| questionText.className = 'font-medium text-red-800'; | |
| questionText.textContent = `${index + 1}. ${quizState.shuffledQuestions[index].question}`; | |
| questionItem.appendChild(questionText); | |
| const userAnswerText = document.createElement('p'); | |
| userAnswerText.className = 'ml-4 mt-1 text-red-600'; | |
| userAnswerText.textContent = `Sua resposta: ${quizState.shuffledAnswers[index][userAnswer].text}`; | |
| questionItem.appendChild(userAnswerText); | |
| const explanationText = document.createElement('p'); | |
| explanationText.className = 'ml-4 mt-1 text-gray-700'; | |
| explanationText.textContent = quizState.shuffledQuestions[index].explanation; | |
| questionItem.appendChild(explanationText); | |
| wrongAnswersContainer.appendChild(questionItem); | |
| } | |
| }); | |
| } else { | |
| const allCorrect = document.createElement('div'); | |
| allCorrect.className = 'bg-green-100 text-green-800 p-3 rounded-md text-center font-medium'; | |
| allCorrect.textContent = 'Parabéns! Você acertou todas as questões!'; | |
| wrongAnswersContainer.appendChild(allCorrect); | |
| } | |
| // Store results | |
| storeResults(correctCount, score); | |
| // Mark as finished and hide quiz screen | |
| quizState.isFinished = true; | |
| quizScreen.classList.add('hidden'); | |
| resultsScreen.classList.remove('hidden'); | |
| } | |
| // Show detailed results | |
| function showDetailedResults() { | |
| const detailedResults = document.createElement('div'); | |
| detailedResults.className = 'mt-6 bg-white rounded-lg shadow-md p-6'; | |
| const title = document.createElement('h3'); | |
| title.className = 'text-xl font-semibold mb-4 text-gray-800'; | |
| title.textContent = 'Resultado Detalhado'; | |
| detailedResults.appendChild(title); | |
| quizState.shuffledQuestions.forEach((question, index) => { | |
| const questionDiv = document.createElement('div'); | |
| questionDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg'; | |
| // Question | |
| const questionText = document.createElement('p'); | |
| questionText.className = 'font-medium text-gray-800 mb-3'; | |
| questionText.textContent = `${index + 1}. ${question.question}`; | |
| questionDiv.appendChild(questionText); | |
| // User answer | |
| const userAnswer = document.createElement('p'); | |
| const isCorrect = checkAnswer(index, quizState.userAnswers[index]); | |
| userAnswer.className = `font-medium ml-4 ${isCorrect ? 'text-green-600' : 'text-red-600'}`; | |
| userAnswer.textContent = `Sua resposta: ${quizState.shuffledAnswers[index][quizState.userAnswers[index]].text}`; | |
| questionDiv.appendChild(userAnswer); | |
| if (!isCorrect) { | |
| // Correct answer | |
| const correctAnswer = question.answers.find(a => a.correct); | |
| if (correctAnswer) { | |
| const correctAnswerText = document.createElement('p'); | |
| correctAnswerText.className = 'font-medium ml-4 text-green-600'; | |
| correctAnswerText.textContent = `Resposta correta: ${correctAnswer.text}`; | |
| questionDiv.appendChild(correctAnswerText); | |
| } | |
| } | |
| // Explanation | |
| if (question.explanation) { | |
| const explanation = document.createElement('p'); | |
| explanation.className = 'ml-4 mt-2 text-gray-700 italic'; | |
| explanation.textContent = question.explanation; | |
| questionDiv.appendChild(explanation); | |
| } | |
| detailedResults.appendChild(questionDiv); | |
| }); | |
| // Add close button | |
| const closeBtn = document.createElement('button'); | |
| closeBtn.className = 'mt-4 bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-150'; | |
| closeBtn.textContent = 'Fechar'; | |
| closeBtn.onclick = () => detailedResults.remove(); | |
| detailedResults.appendChild(closeBtn); | |
| // Insert after wrong answers | |
| wrongAnswersContainer.appendChild(detailedResults); | |
| } | |
| // Store results in localStorage | |
| function storeResults(correctCount, score) { | |
| // Format answers for storage (e.g., "1.c, 2.a, 3.b, ...") | |
| const formattedAnswers = quizState.userAnswers.map((answerIndex, questionIndex) => { | |
| if (answerIndex === null) return `${questionIndex + 1}.não respondida`; | |
| const answerLetters = ['a', 'b', 'c', 'd', 'e']; | |
| return `${questionIndex + 1}.${answerLetters[answerIndex]}`; | |
| }).join(', '); | |
| // Format time | |
| const minutes = Math.floor(quizState.timeElapsed / 60).toString().padStart(2, '0'); | |
| const seconds = (quizState.timeElapsed % 60).toString().padStart(2, '0'); | |
| const formattedTime = `${minutes}:${seconds}`; | |
| // Create result object | |
| const result = { | |
| name: quizState.studentName, | |
| class: quizState.studentClass, | |
| date: quizState.quizDate, | |
| answers: formattedAnswers, | |
| correct: correctCount, | |
| score: score, | |
| time: formattedTime, | |
| timestamp: new Date().toISOString(), | |
| originalOrder: quizState.shuffledQuestions.map(q => questions.findIndex(item => item.question === q.question)) | |
| }; | |
| // Get existing results or create new array | |
| const existingResults = JSON.parse(localStorage.getItem('quizResults')) || []; | |
| // Add new result | |
| existingResults.push(result); | |
| // Save back to localStorage | |
| localStorage.setItem('quizResults', JSON.stringify(existingResults)); | |
| localStorage.setItem('quizCompleted_' + quizState.studentName, 'true'); | |
| } | |
| // Load results for admin panel | |
| function loadResults(filterClass = '') { | |
| const allResults = JSON.parse(localStorage.getItem('quizResults')) || []; | |
| // Clear table | |
| resultsTableBody.innerHTML = ''; | |
| // Add all results to the table (filtering will be handled by filterResults) | |
| allResults.forEach(result => { | |
| const row = document.createElement('tr'); | |
| row.className = 'hover:bg-gray-50'; | |
| // Name | |
| const nameCell = document.createElement('td'); | |
| nameCell.className = 'py-2 px-4'; | |
| nameCell.textContent = result.name; | |
| row.appendChild(nameCell); | |
| // Class | |
| const classCell = document.createElement('td'); | |
| classCell.className = 'py-2 px-4'; | |
| classCell.textContent = result.class; | |
| row.appendChild(classCell); | |
| // Date | |
| const dateCell = document.createElement('td'); | |
| dateCell.className = 'py-2 px-4'; | |
| dateCell.textContent = formatDate(result.date); | |
| row.appendChild(dateCell); | |
| // Answers | |
| const answersCell = document.createElement('td'); | |
| answersCell.className = 'py-2 px-4 font-mono text-sm'; | |
| answersCell.textContent = result.answers; | |
| row.appendChild(answersCell); | |
| // Score | |
| const scoreCell = document.createElement('td'); | |
| scoreCell.className = 'py-2 px-4 font-semibold'; | |
| scoreCell.textContent = `${result.correct}/${questions.length} (${result.score}/10)`; | |
| row.appendChild(scoreCell); | |
| // Time | |
| const timeCell = document.createElement('td'); | |
| timeCell.className = 'py-2 px-4'; | |
| timeCell.textContent = result.time; | |
| row.appendChild(timeCell); | |
| resultsTableBody.appendChild(row); | |
| }); | |
| // Update stats and chart (will be filtered by filterResults) | |
| updateClassStatistics(allResults); | |
| updateClassPieChart(allResults); | |
| results.forEach(result => { | |
| const row = document.createElement('tr'); | |
| row.className = 'hover:bg-gray-50'; | |
| // Name | |
| const nameCell = document.createElement('td'); | |
| nameCell.className = 'py-2 px-4'; | |
| nameCell.textContent = result.name; | |
| row.appendChild(nameCell); | |
| // Class | |
| const classCell = document.createElement('td'); | |
| classCell.className = 'py-2 px-4'; | |
| classCell.textContent = result.class; | |
| row.appendChild(classCell); | |
| // Date | |
| const dateCell = document.createElement('td'); | |
| dateCell.className = 'py-2 px-4'; | |
| dateCell.textContent = formatDate(result.date); | |
| row.appendChild(dateCell); | |
| // Answers | |
| const answersCell = document.createElement('td'); | |
| answersCell.className = 'py-2 px-4 font-mono text-sm'; | |
| answersCell.textContent = result.answers; | |
| row.appendChild(answersCell); | |
| // Score | |
| const scoreCell = document.createElement('td'); | |
| scoreCell.className = 'py-2 px-4 font-semibold'; | |
| scoreCell.textContent = `${result.correct}/${questions.length} (${result.score}/10)`; | |
| row.appendChild(scoreCell); | |
| // Time | |
| const timeCell = document.createElement('td'); | |
| timeCell.className = 'py-2 px-4'; | |
| timeCell.textContent = result.time; | |
| row.appendChild(timeCell); | |
| resultsTableBody.appendChild(row); | |
| }); | |
| } | |
| // Filter results by class | |
| function updateClassStatistics(results) { | |
| const classStatsContainer = document.getElementById('classStats'); | |
| if (!classStatsContainer) return; | |
| // Group results by class | |
| const classes = {}; | |
| results.forEach(result => { | |
| if (!classes[result.class]) { | |
| classes[result.class] = { | |
| count: 0, | |
| totalScore: 0, | |
| totalCorrect: 0 | |
| }; | |
| } | |
| classes[result.class].count++; | |
| classes[result.class].totalScore += result.score; | |
| classes[result.class].totalCorrect += result.correct; | |
| }); | |
| // Generate stats for each class | |
| classStatsContainer.innerHTML = ''; | |
| Object.entries(classes).forEach(([className, stats]) => { | |
| const avgScore = (stats.totalScore / stats.count).toFixed(1); | |
| const avgCorrect = (stats.totalCorrect / (stats.count * questions.length) * 100).toFixed(1); | |
| const statCard = document.createElement('div'); | |
| statCard.className = 'bg-white p-4 rounded-lg shadow-sm border border-gray-200'; | |
| statCard.innerHTML = ` | |
| <h4 class="font-bold text-lg">${className}</h4> | |
| <p class="mt-2"><span class="font-semibold">Alunos:</span> ${stats.count}</p> | |
| <p><span class="font-semibold">Média de notas:</span> ${avgScore}/10</p> | |
| <p><span class="font-semibold">% de acerto:</span> ${avgCorrect}%</p> | |
| `; | |
| classStatsContainer.appendChild(statCard); | |
| }); | |
| // Add overall stats | |
| const totalStudents = results.length; | |
| if (totalStudents > 0) { | |
| const totalScore = results.reduce((sum, r) => sum + r.score, 0); | |
| const totalCorrect = results.reduce((sum, r) => sum + r.correct, 0); | |
| const overallAvgScore = (totalScore / totalStudents).toFixed(1); | |
| const overallAvgCorrect = (totalCorrect / (totalStudents * questions.length) * 100).toFixed(1); | |
| const overallStat = document.createElement('div'); | |
| overallStat.className = 'bg-blue-50 p-4 rounded-lg shadow-sm border border-blue-200 md:col-span-3'; | |
| overallStat.innerHTML = ` | |
| <h4 class="font-bold text-lg text-blue-800">Estatísticas Gerais</h4> | |
| <p class="mt-2"><span class="font-semibold">Total de alunos:</span> ${totalStudents}</p> | |
| <p><span class="font-semibold">Média geral de notas:</span> ${overallAvgScore}/10</p> | |
| <p><span class="font-semibold">% geral de acerto:</span> ${overallAvgCorrect}%</p> | |
| `; | |
| classStatsContainer.appendChild(overallStat); | |
| } | |
| } | |
| function filterResults() { | |
| const selectedClass = classFilter.value; | |
| loadResults(selectedClass); | |
| } | |
| // Update pie chart for class stats | |
| function updateClassPieChart(results) { | |
| // Get container or create it | |
| let chartContainer = document.getElementById('pieChartContainer'); | |
| if (!chartContainer) { | |
| chartContainer = document.createElement('div'); | |
| chartContainer.id = 'pieChartContainer'; | |
| chartContainer.className = 'w-full max-w-md mx-auto mt-6 bg-white p-4 rounded-lg shadow-md'; | |
| chartContainer.innerHTML = '<h4 class="font-bold text-center mb-4">Distribuição de Notas</h4>'; | |
| const chartCanvas = document.createElement('canvas'); | |
| chartCanvas.id = 'resultsPieChart'; | |
| chartContainer.appendChild(chartCanvas); | |
| document.querySelector('#adminPanel .max-w-6xl').appendChild(chartContainer); | |
| } | |
| if (results.length === 0) { | |
| chartContainer.innerHTML = '<p class="text-gray-500 text-center py-4">Nenhum dado disponível para exibir</p>'; | |
| return; | |
| } | |
| // Grade buckets | |
| const gradeBuckets = [0, 0, 0, 0, 0]; // 0-2, 2-4, 4-6, 6-8, 8-10 | |
| results.forEach(result => { | |
| const score = result.score; | |
| if (score <= 2) gradeBuckets[0]++; | |
| else if (score <= 4) gradeBuckets[1]++; | |
| else if (score <= 6) gradeBuckets[2]++; | |
| else if (score <= 8) gradeBuckets[3]++; | |
| else gradeBuckets[4]++; | |
| }); | |
| const ctx = document.getElementById('resultsPieChart').getContext('2d'); | |
| // If existing chart exists, destroy it | |
| if (window.resultsPieChart) { | |
| window.resultsPieChart.destroy(); | |
| } | |
| window.resultsPieChart = new Chart(ctx, { | |
| type: 'pie', | |
| data: { | |
| labels: ['0-2', '2-4', '4-6', '6-8', '8-10'], | |
| datasets: [{ | |
| data: gradeBuckets, | |
| backgroundColor: [ | |
| '#EF4444', | |
| '#F59E0B', | |
| '#3B82F6', | |
| '#6366F1', | |
| '#10B981' | |
| ], | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| position: 'bottom' | |
| }, | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| const total = results.length; | |
| const value = context.raw; | |
| const percentage = Math.round((value / total) * 100); | |
| return `${context.label}: ${value} (${percentage}%)`; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Export results to CSV | |
| function exportResults() { | |
| const results = JSON.parse(localStorage.getItem('quizResults')) || []; | |
| if (results.length === 0) { | |
| alert('Nenhum resultado encontrado para exportar.'); | |
| return; | |
| } | |
| // Create CSV header | |
| let csv = 'Nome,Turma,Data,Respostas,Corretas,Total,Nota,Tempo\n'; | |
| // Add each result to CSV | |
| results.forEach(result => { | |
| csv += `"${result.name}","${result.class}","${formatDate(result.date)}","${result.answers}",${result.correct},${questions.length},${result.score},"${result.time}"\n`; | |
| }); | |
| // Create download link | |
| const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `resultados_questionario_${formatDate(new Date().toISOString())}.csv`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| // Helper function to format date | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('pt-BR'); | |
| } | |
| // Helper function to shuffle array | |
| function shuffleArray(array) { | |
| const newArray = [...array]; | |
| for (let i = newArray.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; | |
| } | |
| return newArray; | |
| } | |
| // Initialize the app when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', initApp); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=asgdestroi/document-rio" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |