diff --git a/MEMORY.md b/MEMORY.md index 9177015..515976b 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -19,7 +19,10 @@ - [x] **Sincronia Total:** Integração via `examId` garantindo que notas do portal preencham automaticamente o boletim administrativo. - [x] **Financeiro Inteligente:** Adicionado suporte ao tipo **"Apostila"** e grupo **"Taxas de Matrícula"** (buscadas dos cards de cursos). Implementado autocompletar inteligente que define o tipo de cobrança baseado na referência selecionada. - [x] **Storage Explorer (MinIO):** Criada interface de gerenciamento de arquivos que permite navegar por buckets (pastas), visualizar (lightbox), baixar e excluir arquivos físicos individualmente. -- [x] **Database Explorer (PostgreSQL):** Implementado explorador dinâmico que lista todas as tabelas do banco em tempo real, mostrando contagem de registros e tamanho ocupado. +- [x] **Database Data Viewer:** Implementada a visualização de registros (linhas) diretamente no Database Explorer, com suporte a redimensionamento automático de colunas e truncamento de dados longos. +- [x] **Controle de Refação (Retake Policy):** Adicionado botão de cadeado nos cards de Avaliações para permitir ou bloquear que alunos refaçam provas no portal (Regra 15). +- [x] **UI de Avaliações:** Padronização dos botões de edição ("Editar Prova" vs "Editar Atividade") e adição de botão de exclusão rápida direto no card. +- [x] **Correção de Vínculo de Notas:** Garantido que o `examId` seja sempre salvo nas notas geradas pelo Portal para preenchimento automático do Boletim Escolar no Manager. - [ ] Próximo Passo: Iniciar testes de estresse no servidor self-hosted para submissão massiva de fotos de frequência. ### 💳 Módulo Financeiro (Portal do Aluno) diff --git a/manager/components/Exams.tsx b/manager/components/Exams.tsx index ef53840..f55cb76 100644 --- a/manager/components/Exams.tsx +++ b/manager/components/Exams.tsx @@ -1,7 +1,9 @@ import React, { useState, useRef } from 'react'; import { SchoolData, Exam, Question } from '../types'; -import { FileText, Plus, Search, BookOpen, Upload, Trash2, ArrowLeft, Save, CheckCircle, Image as ImageIcon, X } from 'lucide-react'; +import { FileText, Plus, Search, BookOpen, Upload, Trash2, ArrowLeft, Save, CheckCircle, Image as ImageIcon, X, RefreshCw, Lock, Unlock } from 'lucide-react'; import { uploadExamImage } from '../services/supabase'; +import { useDialog } from '../DialogContext'; +import { dbService } from '../services/dbService'; interface ExamsProps { data: SchoolData; @@ -13,6 +15,7 @@ const Exams: React.FC = ({ data, updateData }) => { const [currentView, setCurrentView] = useState<'list' | 'builder'>('list'); const [editingExam, setEditingExam] = useState(null); const [isUploading, setIsUploading] = useState(false); + const { showAlert, showConfirm } = useDialog(); const normalizePhotoUrl = (url?: string) => { if (!url) return ''; @@ -50,6 +53,30 @@ const Exams: React.FC = ({ data, updateData }) => { setCurrentView('builder'); }; + const handleToggleRetake = (examId: string) => { + const updatedExams = exams.map(e => { + if (e.id === examId) { + return { ...e, allowRetake: !e.allowRetake }; + } + return e; + }); + updateData({ exams: updatedExams }); + dbService.saveData({ ...data, exams: updatedExams }); + }; + + const handleDeleteExam = (examId: string) => { + showConfirm( + 'Excluir Avaliação', + 'Tem certeza que deseja excluir esta avaliação? Esta ação não pode ser desfeita e notas vinculadas no boletim perderão o vínculo.', + () => { + const updatedExams = exams.filter(e => e.id !== examId); + updateData({ exams: updatedExams }); + dbService.saveData({ ...data, exams: updatedExams }); + showAlert('Sucesso', 'Avaliação excluída com sucesso.', 'success'); + } + ); + }; + const handleAddQuestion = () => { if (!editingExam) return; setEditingExam({ @@ -510,12 +537,28 @@ const Exams: React.FC = ({ data, updateData }) => {

)} -
+
+
+ + +
diff --git a/manager/components/Settings.tsx b/manager/components/Settings.tsx index 3d3cfa0..ac859de 100644 --- a/manager/components/Settings.tsx +++ b/manager/components/Settings.tsx @@ -85,10 +85,14 @@ const Settings: React.FC = ({ data, updateData, setData }) => { const [showDatabaseExplorerModal, setShowDatabaseExplorerModal] = useState(false); const [dbTables, setDbTables] = useState([]); const [loadingDbTables, setLoadingDbTables] = useState(false); + const [selectedDbTable, setSelectedDbTable] = useState(null); + const [tableData, setTableData] = useState<{rows: any[], fields: string[]}>({rows: [], fields: []}); + const [loadingTableData, setLoadingTableData] = useState(false); const openDatabaseExplorer = async () => { setShowDatabaseExplorerModal(true); setLoadingDbTables(true); + setSelectedDbTable(null); try { const res = await fetch('/api/database/tables'); const data = await res.json(); @@ -101,7 +105,20 @@ const Settings: React.FC = ({ data, updateData, setData }) => { } }; - + const openTable = async (tableName: string) => { + setSelectedDbTable(tableName); + setLoadingTableData(true); + try { + const res = await fetch(`/api/database/tables/${tableName}/data`); + const data = await res.json(); + setTableData({ rows: data.rows || [], fields: data.fields || [] }); + } catch (e) { + console.error(e); + showAlert('Erro', 'Não foi possível carregar os dados da tabela.', 'error'); + } finally { + setLoadingTableData(false); + } + }; const openBucket = async (bucketName: string) => { setSelectedStorageBucket(bucketName); setLoadingBucket(true); @@ -899,18 +916,24 @@ const Settings: React.FC = ({ data, updateData, setData }) => { {/* Header */}
-
- -
+ {selectedDbTable ? ( + + ) : ( +
+ +
+ )}
-

Database Explorer

+

{selectedDbTable ? selectedDbTable : 'Database Explorer'}

- {dbTables.length} tabelas no schema public do PostgreSQL. + {selectedDbTable ? `${tableData.rows.length} registros exibidos.` : `${dbTables.length} tabelas no schema public do PostgreSQL.`}

-
+
+ {/* Overlay transparente para fechar ao clicar fora */} +
setPreviewUrl(null)}>
+ +
+ {/* Botão de fechar fixado na moldura */} + + {previewUrl.match(/\.(jpeg|jpg|gif|png|webp)$/i) ? ( - Preview + Preview ) : ( -