diff --git a/GEMINI.md b/GEMINI.md index d6b34d9..b57f685 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -27,3 +27,6 @@ 5. **Build & Deploy Stability:** O pipeline de deploy deve obrigatoriamente utilizar `runs-on: self-hosted` e compilar apenas a plataforma `linux/arm64` (sem emulação QEMU). A atualização da stack em produção deve ser automatizada via container transiente do Watchtower. 6. **Express Compatibility**: Avoid using raw `/*` wildcards in Express 5 routes; use Regex paths (`/^\/route\/(.+)$/`) for compatibility with `path-to-regexp` v8. 7. **Frontend Independence**: NEVER import files from `services/` or `server.js` directly into React components to prevent Node.js/SDK leakage (causes White Screen). Physical isolation is enforced: backend-only services (like MinIO/S3 storage) MUST stay outside the `src/` directory in Vite/React projects. Use `helpers.ts` for UI logic and standard `fetch` for API calls. +8. **Login Persistence**: Administrative sessions are persisted via `localStorage` ('edumanager_session'). The main entry point MUST validate the session on mount to ensure UX continuity. +9. **Real-time & Sync**: In self-hosted environments, use **Intelligent Polling (30s)** to synchronize notifications and critical data between Portal and Manager, as standard Supabase Realtime is disabled. +10. **Justification Logic**: Attendance justifications MUST include `fromStudentId` in notification metadata and support both `arquivo` and `arquivo_base64` keys for attachment compatibility. diff --git a/MEMORY.md b/MEMORY.md index a4cc17a..943c6ec 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -11,11 +11,12 @@ - [x] Correção do Crash 404 no Portal: Injeção da pasta `src/services` no container de produção para permitir o import do `storage.js`. - [x] Correção das Imagens de Prova: Normalização das URLs nas questões de avaliações (Portal e Manager). - [x] Estabilização de CI/CD: Transição para `runs-on: self-hosted` (ARM64 nativo) eliminando lentidão e crashes do QEMU. -- [x] Fix Tela Branca (Portal): Isolamento Físico absoluto do `storage.js` (SDK Backend) para pasta fora do `src` (server-only). Isso impede vazamentos de Node.js no navegador. -- [x] Correção Financeiro (Portal): Resolvido erros de renderização em `Financeiro.tsx` e inconsistência de tipos em `Notifications.ts`. -- [x] Pipeline Deploy: Ajustado volume do Docker no GitHub Actions de `~/.docker` para `$DOCKER_CONFIG` para compatibilidade total com o Runner. -- [x] Normalização de Imagens: Todas as fotos de alunos no Portal agora passam pela vacina `normalizePhotoUrl`. -- [ ] Próximo Passo: Verificar se o Watchtower sincronizou as imagens corretamente na produção. +- [x] Correção do Sino de Notificações: Botões sempre visíveis e suporte a anexo via chave `arquivo`. +- [x] Persistência de Login (Manager): Login agora persiste no F5 via `localStorage`. +- [x] Polling de Dados (30s): Implementada sincronização automática entre Portal e Manager para notificações instantâneas (Self-Hosted). +- [x] Deep Link de Notificação: Corrigida navegação do Sino para o Histórico de Aluno usando metadados `fromStudentId`. +- [x] Normalização de Anexos: Sincronização de chaves de justificativa entre Portal e página de Frequência. +- [ ] Próximo Passo: Monitorar o log de acesso dos usuários após a ativação da persistência de login. ### 💳 Módulo Financeiro (Portal do Aluno) - **Funcionalidades Implementadas:** diff --git a/manager/components/AdminNotifications.tsx b/manager/components/AdminNotifications.tsx index d24286f..7845dfa 100644 --- a/manager/components/AdminNotifications.tsx +++ b/manager/components/AdminNotifications.tsx @@ -166,12 +166,14 @@ const AdminNotifications: React.FC = ({ data, updateData, setView, onNavi const isJustificativa = notif.title.toLowerCase().includes('justificativa') || notif.message.toLowerCase().includes('justificativa'); let displayMessage = notif.message; + let justificationMotive = ''; let attachmentFromMessage = null; if (notif.message.startsWith('{')) { try { const parsed = JSON.parse(notif.message); - displayMessage = parsed.motivo || displayMessage; + displayMessage = parsed.text || parsed.motivo || displayMessage; + justificationMotive = parsed.motivo || ''; attachmentFromMessage = parsed.arquivo || parsed.arquivo_base64 || null; } catch(e) {} } @@ -192,6 +194,12 @@ const AdminNotifications: React.FC = ({ data, updateData, setView, onNavi

{displayMessage}

+ {isJustificativa && justificationMotive && ( +
+

Motivo enviado:

+

"{justificationMotive}"

+
+ )} {(!notif.read) && (
{isJustificativa && ( @@ -248,7 +256,7 @@ const AdminNotifications: React.FC = ({ data, updateData, setView, onNavi )} {viewingAttachment && ( -
+

diff --git a/manager/components/AttendanceQuery.tsx b/manager/components/AttendanceQuery.tsx index a2e9d51..365d877 100644 --- a/manager/components/AttendanceQuery.tsx +++ b/manager/components/AttendanceQuery.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { SchoolData, Attendance, Class, Student } from '../types'; import { dbService } from '../services/dbService'; import { useDialog } from '../DialogContext'; -import { Search, Calendar, User, Clock, CheckCircle, XCircle, FileDown, BookOpen, Plus, X, AlertCircle, RefreshCw, ChevronRight, Trash2, FileSignature, Paperclip } from 'lucide-react'; +import { Search, Calendar, User, Clock, CheckCircle, XCircle, FileDown, BookOpen, Plus, X, AlertCircle, RefreshCw, ChevronRight, Trash2, FileSignature, Paperclip, Eye } from 'lucide-react'; import jsPDF from 'jspdf'; import 'jspdf-autotable'; import { addHeader } from '../services/pdfService'; @@ -48,6 +48,9 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee const [absenceLessonId, setAbsenceLessonId] = useState(''); const [viewingAttachment, setViewingAttachment] = useState(null); const [attendanceForAttachment, setAttendanceForAttachment] = useState(null); + const [showJustificationTextModal, setShowJustificationTextModal] = useState(false); + const [currentJustificationText, setCurrentJustificationText] = useState(''); + const [currentRecordForJustification, setCurrentRecordForJustification] = useState(null); // Helper para normalizar URLs de fotos (vacina contra cache antigo) const normalizePhotoUrl = (url?: string) => { @@ -654,7 +657,18 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee {isAwaiting || isPendente ? ( Aguardando registro ou justificativa... ) : justMotivo ? ( -

{justMotivo}

+ ) : ( )} @@ -860,6 +874,55 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee

)} + {showJustificationTextModal && ( +
+
+ {/* Design header */} +
+ +
+

+ Motivo da Falta +

+ +
+ +
+
+

+ "{currentJustificationText}" +

+
+ + {currentRecordForJustification && !currentRecordForJustification.justificationAccepted && ( + + )} + + {currentRecordForJustification?.justificationAccepted && ( +
+ Já Aceita +
+ )} +
+
+
+ )}
); }; diff --git a/portal/server.selfhosted.js b/portal/server.selfhosted.js index 84e31ef..216e7fc 100644 --- a/portal/server.selfhosted.js +++ b/portal/server.selfhosted.js @@ -312,7 +312,10 @@ app.post('/api/portal/frequencia/justificar', authMiddleware, upload.single('arq studentId: 'admin', fromStudentId: req.user.studentId, // Identificador para navegação no Manager title: 'Nova Justificativa de Falta', - message: `${student?.name || 'Aluno'} enviou uma justificativa para a aula de ${date}.`, + message: JSON.stringify({ + text: `${student?.name || 'Aluno'} enviou uma justificativa para a aula de ${date}.`, + motivo: motivo.trim() + }), attachment: publicUrl, read: false, createdAt: new Date().toISOString(),