fix: corrige import da sidebar na tela do portal
This commit is contained in:
parent
a970f8a65a
commit
147ea6c264
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)',
|
||||||
}} />
|
}} />
|
||||||
|
|
|
||||||
|
|
@ -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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue