fix: corrige tela preta da camera traseira e aplica correcao global de imagens

This commit is contained in:
Sidney 2026-04-21 22:29:17 -03:00
parent 980b15905b
commit 167c8431eb
7 changed files with 117 additions and 27 deletions

View File

@ -49,6 +49,20 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
const [viewingAttachment, setViewingAttachment] = useState<string | null>(null); const [viewingAttachment, setViewingAttachment] = useState<string | null>(null);
const [attendanceForAttachment, setAttendanceForAttachment] = useState<Attendance | null>(null); const [attendanceForAttachment, setAttendanceForAttachment] = useState<Attendance | null>(null);
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
const normalizePhotoUrl = (url?: string) => {
if (!url || typeof url !== 'string') return '';
if (url.startsWith('data:image') || url.startsWith('blob:')) return url;
if (url.startsWith('/storage/')) return url;
try {
const match = url.match(/^https?:\/\/[^\/]+\/(.+)$/);
if (match) return `/storage/${match[1]}`;
} catch(e) {}
return url;
};
const toggleAttendanceStatus = (record: any) => { const toggleAttendanceStatus = (record: any) => {
let updatedAttendance = [...(data.attendance || [])]; let updatedAttendance = [...(data.attendance || [])];
@ -404,9 +418,9 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
className="flex items-center justify-between p-4 bg-slate-50 hover:bg-indigo-50 rounded-xl border border-slate-100 hover:border-indigo-200 cursor-pointer transition-all group" className="flex items-center justify-between p-4 bg-slate-50 hover:bg-indigo-50 rounded-xl border border-slate-100 hover:border-indigo-200 cursor-pointer transition-all group"
> >
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 font-black text-sm flex-shrink-0 overflow-hidden"> <div className="w-10 h-10 rounded-full bg-slate-100 overflow-hidden border border-slate-200 flex items-center justify-center text-indigo-600 font-black text-sm flex-shrink-0">
{student.photo ? ( {student.photo ? (
<img src={student.photo} alt={student.name} className="w-full h-full object-cover" /> <img src={normalizePhotoUrl(student.photo)} alt={student.name} className="w-full h-full object-cover" />
) : ( ) : (
student.name.charAt(0).toUpperCase() student.name.charAt(0).toUpperCase()
)} )}
@ -441,11 +455,13 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
<div className="p-6 border-b border-slate-100 flex items-center justify-between bg-slate-50/50"> <div className="p-6 border-b border-slate-100 flex items-center justify-between bg-slate-50/50">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 font-black overflow-hidden flex-shrink-0"> <div className="w-20 h-20 rounded-full bg-slate-50 border-4 border-white shadow-xl overflow-hidden flex-shrink-0">
{selectedStudent.photo ? ( {selectedStudent.photo ? (
<img src={selectedStudent.photo} alt={selectedStudent.name} className="w-full h-full object-cover" /> <img src={normalizePhotoUrl(selectedStudent.photo)} alt={selectedStudent.name} className="w-full h-full object-cover" />
) : ( ) : (
<User size={24} /> <div className="w-full h-full flex items-center justify-center bg-indigo-100 text-indigo-600 font-black text-2xl">
{selectedStudent.name.charAt(0).toUpperCase()}
</div>
)} )}
</div> </div>
<div> <div>
@ -834,10 +850,10 @@ const AttendanceQuery: React.FC<AttendanceQueryProps> = ({ data, updateData, dee
</div> </div>
</div> </div>
<div className="flex-1 overflow-auto bg-slate-200 p-4 flex items-center justify-center"> <div className="flex-1 overflow-auto bg-slate-200 p-4 flex items-center justify-center">
{viewingAttachment.startsWith('data:application/pdf') || viewingAttachment.includes('.pdf') ? ( {viewingAttachment.includes('pdf') ? (
<iframe src={viewingAttachment} className="w-full h-full min-h-[70vh] rounded-lg shadow-sm bg-white" /> <iframe src={normalizePhotoUrl(viewingAttachment)} className="w-full h-full min-h-[70vh] rounded-lg shadow-sm border border-slate-100" />
) : ( ) : (
<img src={viewingAttachment} className="max-w-full max-h-full object-contain rounded-lg shadow-sm" alt="Documento" /> <img src={normalizePhotoUrl(viewingAttachment)} className="max-w-full max-h-full object-contain rounded-lg shadow-xl" alt="Documento" />
)} )}
</div> </div>
</div> </div>

View File

@ -21,6 +21,20 @@ const Classes: React.FC<ClassesProps> = ({ data, updateData, onNavigateToClass }
const [scheduleClass, setScheduleClass] = useState<Class | null>(null); // For LessonSchedule component const [scheduleClass, setScheduleClass] = useState<Class | null>(null); // For LessonSchedule component
const [viewingStudentsClass, setViewingStudentsClass] = useState<Class | null>(null); // For student list modal const [viewingStudentsClass, setViewingStudentsClass] = useState<Class | null>(null); // For student list modal
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
const normalizePhotoUrl = (url?: string) => {
if (!url || typeof url !== 'string') return '';
if (url.startsWith('data:image') || url.startsWith('blob:')) return url;
if (url.startsWith('/storage/')) return url;
try {
const match = url.match(/^https?:\/\/[^\/]+\/(.+)$/);
if (match) return `/storage/${match[1]}`;
} catch(e) {}
return url;
};
const [formData, setFormData] = useState<Omit<Class, 'id'>>({ const [formData, setFormData] = useState<Omit<Class, 'id'>>({
name: '', name: '',
courseId: '', courseId: '',
@ -531,7 +545,7 @@ const Classes: React.FC<ClassesProps> = ({ data, updateData, onNavigateToClass }
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-slate-100 overflow-hidden border border-slate-200"> <div className="w-10 h-10 rounded-full bg-slate-100 overflow-hidden border border-slate-200">
{student.photo ? ( {student.photo ? (
<img src={student.photo} alt={student.name} className="w-full h-full object-cover" /> <img src={normalizePhotoUrl(student.photo)} alt={student.name} className="w-full h-full object-cover" />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center text-slate-300"> <div className="w-full h-full flex items-center justify-center text-slate-300">
<User size={20} /> <User size={20} />

View File

@ -39,6 +39,20 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
const periods = data.periods || []; const periods = data.periods || [];
const grades = data.grades || []; const grades = data.grades || [];
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
const normalizePhotoUrl = (url?: string) => {
if (!url || typeof url !== 'string') return '';
if (url.startsWith('data:image') || url.startsWith('blob:')) return url;
if (url.startsWith('/storage/')) return url;
try {
const match = url.match(/^https?:\/\/[^\/]+\/(.+)$/);
if (match) return `/storage/${match[1]}`;
} catch(e) {}
return url;
};
const handleAddSubject = () => { const handleAddSubject = () => {
if (!newSubjectName.trim()) { if (!newSubjectName.trim()) {
showAlert('Atenção', '⚠️ Por favor, informe o nome da disciplina.', 'warning'); showAlert('Atenção', '⚠️ Por favor, informe o nome da disciplina.', 'warning');
@ -364,7 +378,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
<div className="flex items-center gap-3 flex-1 min-w-0"> <div className="flex items-center gap-3 flex-1 min-w-0">
<div className="w-10 h-10 rounded-full bg-white border border-slate-200 flex items-center justify-center text-slate-400 flex-shrink-0 overflow-hidden"> <div className="w-10 h-10 rounded-full bg-white border border-slate-200 flex items-center justify-center text-slate-400 flex-shrink-0 overflow-hidden">
{student.photo ? ( {student.photo ? (
<img src={student.photo} alt={student.name} className="w-full h-full object-cover" /> <img src={normalizePhotoUrl(student.photo)} alt={student.name} className="w-full h-full object-cover" />
) : ( ) : (
<User size={20} /> <User size={20} />
)} )}

View File

@ -58,6 +58,20 @@ const Settings: React.FC<SettingsProps> = ({ data, updateData, setData }) => {
const [activeTab, setActiveTab] = useState<'perfil' | 'monitoramento'>('perfil'); const [activeTab, setActiveTab] = useState<'perfil' | 'monitoramento'>('perfil');
const [apiLogs, setApiLogs] = useState<any[]>([]); const [apiLogs, setApiLogs] = useState<any[]>([]);
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
const normalizePhotoUrl = (url?: string) => {
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;
};
const [systemStats, setSystemStats] = useState<any>(null); const [systemStats, setSystemStats] = useState<any>(null);
const fetchStats = () => { const fetchStats = () => {
@ -334,7 +348,9 @@ const Settings: React.FC<SettingsProps> = ({ data, updateData, setData }) => {
<div className="flex flex-col items-center gap-4"> <div className="flex flex-col items-center gap-4">
<div className="w-40 h-40 rounded-xl bg-slate-50 border-2 border-dashed border-slate-200 flex items-center justify-center overflow-hidden relative group shadow-inner"> <div className="w-40 h-40 rounded-xl bg-slate-50 border-2 border-dashed border-slate-200 flex items-center justify-center overflow-hidden relative group shadow-inner">
{globalLogo ? ( {globalLogo ? (
<img src={globalLogo} alt="Logo" className="w-full h-full object-contain p-2" /> <div className="w-full h-full bg-slate-50 flex items-center justify-center p-4">
<img src={normalizePhotoUrl(globalLogo)} alt="Logo" className="w-full h-full object-contain p-2" />
</div>
) : ( ) : (
<div className="text-slate-300 text-center p-4"> <div className="text-slate-300 text-center p-4">
<Camera size={40} className="mx-auto mb-2 opacity-20" /> <Camera size={40} className="mx-auto mb-2 opacity-20" />

View File

@ -99,6 +99,21 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
} }
}, [deepLinkStudentId, deepLinkClassId, data.students]); }, [deepLinkStudentId, deepLinkClassId, data.students]);
// Helper para normalizar URLs de fotos (vacina contra cache antigo)
const normalizePhotoUrl = (url?: string) => {
if (!url || typeof url !== 'string') return '';
if (url.startsWith('data:image')) return url; // Base64
if (url.startsWith('/storage/')) return url; // Já formatada
// Converte URLs absolutas (ex: https://storageedu.../alunos/file) para proxy relativo (/storage/alunos/file)
try {
const match = url.match(/^https?:\/\/[^\/]+\/(.+)$/);
if (match) return `/storage/${match[1]}`;
} catch(e) {}
return url;
};
// Load Models // Load Models
useEffect(() => { useEffect(() => {
const loadModels = async () => { const loadModels = async () => {
@ -516,6 +531,9 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
}); });
streamRef.current = stream; streamRef.current = stream;
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
setCameraActive(true); setCameraActive(true);
} catch (err) { } catch (err) {
console.error("Error accessing camera:", err); console.error("Error accessing camera:", err);
@ -1208,7 +1226,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-slate-100 overflow-hidden border border-slate-200"> <div className="w-10 h-10 rounded-full bg-slate-100 overflow-hidden border border-slate-200">
{student.photo ? ( {student.photo ? (
<img src={student.photo} alt={student.name} className="w-full h-full object-cover" /> <img src={normalizePhotoUrl(student.photo)} alt={student.name} className="w-full h-full object-cover" />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center text-slate-400"> <div className="w-full h-full flex items-center justify-center text-slate-400">
<User size={20} /> <User size={20} />
@ -1358,7 +1376,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
</> </>
) : formData.photo ? ( ) : formData.photo ? (
<> <>
<img src={formData.photo} alt="Student" className="w-full h-full object-cover" /> <img src={normalizePhotoUrl(formData.photo)} alt="Student" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center"> <div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<p className="text-white font-bold text-xs">Alterar Foto</p> <p className="text-white font-bold text-xs">Alterar Foto</p>
</div> </div>

View File

@ -52,9 +52,11 @@ const lockCache = new Set();
// ============================================================ // ============================================================
// 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/:bucket/*', async (req, res) => {
try { try {
const { bucket, key } = req.params; const bucket = req.params.bucket;
const key = req.params[0]; // Captura tudo que vem após o bucket (incluindo barras)
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);
@ -62,6 +64,7 @@ app.get('/storage/:bucket/:key', async (req, res) => {
res.set('Cache-Control', 'public, max-age=86400'); res.set('Cache-Control', 'public, max-age=86400');
data.Body.pipe(res); data.Body.pipe(res);
} catch (e) { } catch (e) {
console.error(`[Storage Proxy] Erro ao buscar: ${req.params.bucket}/${req.params[0]}`, e.message);
res.status(404).send('Arquivo não encontrado'); res.status(404).send('Arquivo não encontrado');
} }
}); });
@ -179,25 +182,32 @@ app.put('/api/school-data', async (req, res) => {
}); });
app.get('/api/system-stats', async (req, res) => { app.get('/api/system-stats', async (req, res) => {
let postgresStats = { dbSize: 'N/A', tableCount: '0' };
try { try {
const dbResult = await pool.query(` const dbResult = await pool.query(`
SELECT pg_size_pretty(pg_database_size(current_database())) as db_size, SELECT pg_size_pretty(pg_database_size(current_database())) as db_size,
(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public') as table_count (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public') as table_count
`); `);
postgresStats = {
const minioStats = await getMinioStats(); dbSize: dbResult.rows[0].db_size,
tableCount: dbResult.rows[0].table_count
res.json({ };
postgres: {
dbSize: dbResult.rows[0].db_size,
tableCount: dbResult.rows[0].table_count
},
minio: minioStats
});
} catch(e) { } catch(e) {
console.error('System Stats Error:', e); console.error('System Stats (Postgres) Error:', e);
res.status(500).json({ error: e.message });
} }
let minioStats = { error: true, message: 'Not initialized' };
try {
minioStats = await getMinioStats();
} catch(e) {
console.error('System Stats (MinIO) Error:', e);
minioStats = { error: true, message: e.message };
}
res.json({
postgres: postgresStats,
minio: minioStats
});
}); });
// ============================================================ // ============================================================

2
manager/test-db.js Normal file
View File

@ -0,0 +1,2 @@
import { pool } from './services/database.js';
pool.query("SELECT pg_size_pretty(pg_database_size(current_database())) as db_size").then(console.log).catch(console.log).finally(() => pool.end());