fix: corrige import da sidebar na tela do portal

This commit is contained in:
Sidney 2026-04-22 06:44:52 -03:00
parent a970f8a65a
commit 147ea6c264
12 changed files with 45 additions and 11 deletions

View File

@ -6,9 +6,11 @@
## 🛠️ Stack Tecnológica ## 🛠️ Stack Tecnológica
- **Frontend/Backend:** Remix (React) - **Frontend/Backend:** Remix (React)
- **Banco de Dados:** PostgreSQL (100% Local/Self-Hosted) - **Banco de Dados:** PostgreSQL (100% Local/Self-Hosted)
- **Arquitetura de Storage:** Self-Hosted (MinIO) - 100% Desacoplado do Supabase Cloud. - **Storage Architecture**: 100% Self-Hosted (MinIO) - Decoupled from Supabase Cloud.
- **Sincronização:** API Local de alta performance para conciliação bancária (Asaas). - **Image Serving**: All images are served via a backend proxy route (`/storage/:bucket/:key`) to ensure cross-origin compatibility and security.
- **Synchronization**: High-performance local API for bank reconciliation (Asaas).
- **Orquestração:** Portainer (Docker) - **Orquestração:** Portainer (Docker)
- **Production Entry Point**: All production deployments MUST use `server.selfhosted.js` renamed/copied as `server.js` in the Docker containers to ensure full local feature availability.
## ⚠️ Regras de Negócio Críticas (MANDATÓRIO) ## ⚠️ Regras de Negócio Críticas (MANDATÓRIO)
@ -22,3 +24,5 @@
2. **Segurança:** Todas as rotas sensíveis devem validar o token JWT local (via secrets do ambiente). Proibido usar Supabase SDK para lógica de autenticação ou sincronização no frontend. 2. **Segurança:** Todas as rotas sensíveis devem validar o token JWT local (via secrets do ambiente). Proibido usar Supabase SDK para lógica de autenticação ou sincronização no frontend.
3. **Resiliência:** Tratamento rigoroso de erros em chamadas de API de terceiros (Asaas, Evolution API). 3. **Resiliência:** Tratamento rigoroso de erros em chamadas de API de terceiros (Asaas, Evolution API).
4. **Upload de Arquivos:** Proibido o uso de Base64 para envio de novos arquivos ao servidor. Use obrigatoriamente `FormData` e envie o objeto `File/Blob` para as rotas de API que integram com o MinIO. 4. **Upload de Arquivos:** Proibido o uso de Base64 para envio de novos arquivos ao servidor. Use obrigatoriamente `FormData` e envie o objeto `File/Blob` para as rotas de API que integram com o MinIO.
5. **Build Stability**: Dockerfiles MUST include `ENV NODE_OPTIONS="--max-old-space-size=4096"` before `npm run build` to prevent Vite/Rolldown crashes during ARM64 cross-compilation.
6. **Express Compatibility**: Avoid using raw `/*` wildcards in Express 5 routes; use Regex paths (`/^\/route\/(.+)$/`) for compatibility with `path-to-regexp` v8.

View File

@ -2,6 +2,14 @@
## 📅 Estado Atual (21/04/2026) ## 📅 Estado Atual (21/04/2026)
- [x] Correção do "Bug da Tela Preta" na câmera ao alternar para câmera traseira no celular.
- [x] Unificação do servidor de produção: Dockerfile agora utiliza `server.selfhosted.js` (Manager e Portal).
- [x] Correção dos Cards de Monitoramento (PostgreSQL/MinIO) com tratativa de erro independente.
- [x] Vacina de cache global: Injeção de `normalizePhotoUrl` nos módulos de Boletim, Turmas e Frequência.
- [x] Estabilização do Build ARM64: Injeção de `max_old_space_size=4096` nos Dockerfiles para evitar crashes do Vite no Github Actions.
- [x] Correção de Rota Express 5: Migração de curingas `*` para Regex para evitar falhas de inicialização no servidor.
- [ ] Próximo Passo: Validar o tempo de build no Github Actions e confirmar o carregamento das fotos na VPS Oracle.
### 💳 Módulo Financeiro (Portal do Aluno) ### 💳 Módulo Financeiro (Portal do Aluno)
- **Funcionalidades Implementadas:** - **Funcionalidades Implementadas:**
- Cards de resumo (Total em Aberto, Pago, Parcelas). - Cards de resumo (Total em Aberto, Pago, Parcelas).

BIN
diff_manager.txt Normal file

Binary file not shown.

BIN
diff_portal.txt Normal file

Binary file not shown.

View File

@ -4,6 +4,7 @@ WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN npm install RUN npm install
COPY . . COPY . .
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm run build RUN npm run build
# ---- Production Stage ---- # ---- Production Stage ----
@ -11,7 +12,7 @@ FROM node:22-alpine AS production
WORKDIR /app WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN npm install --omit=dev RUN npm install --omit=dev
COPY server.js ./ COPY server.selfhosted.js ./server.js
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist
EXPOSE 3001 EXPOSE 3001
CMD ["node", "server.js"] CMD ["node", "server.js"]

View File

@ -5,10 +5,10 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"dev:server": "node server.js", "dev:server": "node server.selfhosted.js",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"start": "node server.js" "start": "node server.selfhosted.js"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.2.2", "@tailwindcss/vite": "^4.2.2",

View File

@ -45,9 +45,10 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
// ============================================================ // ============================================================
// Proxy de Imagens do MinIO (acesso público via backend) // Proxy de Imagens do MinIO (acesso público via backend)
// ============================================================ // ============================================================
app.get('/storage/:bucket/:key', async (req, res) => { app.get(/^\/storage\/([^\/]+)\/(.+)$/, async (req, res) => {
try { try {
const { bucket, key } = req.params; const bucket = req.params[0];
const key = req.params[1];
const command = new GetObjectCommand({ Bucket: bucket, Key: key }); const command = new GetObjectCommand({ Bucket: bucket, Key: key });
const data = await s3Client.send(command); const data = await s3Client.send(command);

View File

@ -3,6 +3,7 @@ import { useTheme } from '../context/ThemeContext';
import { Moon, Sun } from 'lucide-react'; import { Moon, Sun } from 'lucide-react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import Notifications from './Notifications'; import Notifications from './Notifications';
import { normalizePhotoUrl } from '../services/storage';
const pageTitles: Record<string, string> = { const pageTitles: Record<string, string> = {
'/': 'Dashboard', '/': 'Dashboard',

View File

@ -5,6 +5,7 @@ import {
FileText, Award, User, LogOut, GraduationCap, X, Menu, ClipboardList FileText, Award, User, LogOut, GraduationCap, X, Menu, ClipboardList
} from 'lucide-react'; } from 'lucide-react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { normalizePhotoUrl } from '../services/storage';
const navItems = [ const navItems = [
{ path: '/', label: 'Dashboard', icon: LayoutDashboard }, { path: '/', label: 'Dashboard', icon: LayoutDashboard },
@ -111,7 +112,7 @@ export default function Sidebar() {
}}> }}>
{schoolLogo ? ( {schoolLogo ? (
<img <img
src={schoolLogo} src={normalizePhotoUrl(schoolLogo)}
alt="EduManager" alt="EduManager"
style={{ style={{
width: desktopCollapsed ? 40 : 44, height: desktopCollapsed ? 40 : 44, width: desktopCollapsed ? 40 : 44, height: desktopCollapsed ? 40 : 44,

View File

@ -4,6 +4,7 @@ import {
User, Mail, Phone, Calendar, MapPin, CreditCard, User, Mail, Phone, Calendar, MapPin, CreditCard,
Lock, Eye, EyeOff, Loader2, CheckCircle2, Shield Lock, Eye, EyeOff, Loader2, CheckCircle2, Shield
} from 'lucide-react'; } from 'lucide-react';
import { normalizePhotoUrl } from '../services/storage';
export default function MeusDados() { export default function MeusDados() {
const { student, updatePassword } = useAuth(); const { student, updatePassword } = useAuth();
@ -98,7 +99,7 @@ export default function MeusDados() {
borderBottom: '1px solid var(--glass-border)', borderBottom: '1px solid var(--glass-border)',
}}> }}>
{student.photo ? ( {student.photo ? (
<img src={student.photo} alt={student.name} style={{ <img src={normalizePhotoUrl(student.photo)} alt={student.name} style={{
width: 64, height: 64, borderRadius: '50%', objectFit: 'cover', width: 64, height: 64, borderRadius: '50%', objectFit: 'cover',
border: '3px solid var(--color-primary)', border: '3px solid var(--color-primary)',
}} /> }} />

View File

@ -46,10 +46,26 @@ export async function uploadFile(bucket, fileName, fileBuffer, contentType) {
} }
/** /**
* Gera a URL pública de um arquivo existente * Normaliza URLs de mídia para o formato de proxy (/storage/...)
*/
export function normalizePhotoUrl(url) {
if (!url || typeof url !== 'string') return '';
if (url.startsWith('data:image')) return url;
if (url.startsWith('/storage/')) return url;
try {
const match = url.match(/^https?:\/\/[^\/]+\/(.+)$/);
if (match) return `/storage/${match[1]}`;
} catch(e) {}
return url;
}
/**
* Gera a URL de proxy para um arquivo existente
*/ */
export function getPublicUrl(bucket, fileName) { export function getPublicUrl(bucket, fileName) {
return `${MINIO_PUBLIC_URL}/${bucket}/${fileName}`; return `/storage/${bucket}/${fileName}`;
} }
/** /**

View File

@ -7,6 +7,7 @@
"skipLibCheck": true, "skipLibCheck": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"allowJs": true,
"isolatedModules": true, "isolatedModules": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,