feat(pre-matricula): add upload progress indicators for folder flyers and adjust backend compression to 1200px
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m8s Details

This commit is contained in:
Sidney 2026-07-18 22:06:33 -03:00
parent ec903c4c9c
commit 14e12d2096
2 changed files with 93 additions and 13 deletions

View File

@ -54,6 +54,8 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
const [showNewField, setShowNewField] = useState(false); const [showNewField, setShowNewField] = useState(false);
const [newField, setNewField] = useState({ label: '', tipo: 'text', placeholder: '', obrigatorio: false, opcoes: '' }); const [newField, setNewField] = useState({ label: '', tipo: 'text', placeholder: '', obrigatorio: false, opcoes: '' });
const [uploadProgress, setUploadProgress] = useState<number | null>(null); const [uploadProgress, setUploadProgress] = useState<number | null>(null);
const [newBannerProgress, setNewBannerProgress] = useState<number | null>(null);
const [editBannerProgress, setEditBannerProgress] = useState<number | null>(null);
const [showMassModal, setShowMassModal] = useState(false); const [showMassModal, setShowMassModal] = useState(false);
const [targetType, setTargetType] = useState('todos'); const [targetType, setTargetType] = useState('todos');
@ -672,6 +674,20 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
</button> </button>
</div> </div>
</div> </div>
) : newBannerProgress !== null ? (
<div className="border border-slate-200 rounded-xl p-5 bg-slate-50 flex flex-col items-center justify-center space-y-3 shadow-inner h-32">
<RefreshCw size={20} className="animate-spin text-indigo-600" />
<div className="text-center">
<p className="text-[10px] font-bold text-slate-700">Enviando e compactando folder...</p>
<p className="text-[9px] text-slate-400 font-bold mt-0.5">{newBannerProgress}% concluído</p>
</div>
<div className="w-full bg-slate-200 rounded-full h-1.5 overflow-hidden shadow-inner">
<div
className="bg-indigo-600 h-full rounded-full transition-all duration-300 ease-out"
style={{ width: `${newBannerProgress}%` }}
></div>
</div>
</div>
) : ( ) : (
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-slate-200 border-dashed rounded-xl cursor-pointer bg-white hover:bg-slate-50 border-slate-300 transition-all"> <label className="flex flex-col items-center justify-center w-full h-32 border-2 border-slate-200 border-dashed rounded-xl cursor-pointer bg-white hover:bg-slate-50 border-slate-300 transition-all">
<div className="flex flex-col items-center justify-center pt-3 pb-3"> <div className="flex flex-col items-center justify-center pt-3 pb-3">
@ -688,13 +704,38 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
if (!file) return; if (!file) return;
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
try {
const res = await fetch('/api/prematricula/upload', { method: 'POST', body: formData }); setNewBannerProgress(0);
if (res.ok) { const xhr = new XMLHttpRequest();
const result = await res.json();
setNewField(prev => ({ ...prev, placeholder: result.url })); xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
setNewBannerProgress(percent);
} }
} catch (err) { console.error(err); } });
xhr.addEventListener('load', () => {
setNewBannerProgress(null);
if (xhr.status >= 200 && xhr.status < 300) {
try {
const result = JSON.parse(xhr.responseText);
setNewField(prev => ({ ...prev, placeholder: result.url }));
} catch (err) {
showAlert('Erro', 'Falha ao processar resposta do servidor.', 'error');
}
} else {
showAlert('Erro', 'Falha ao carregar folder.', 'error');
}
});
xhr.addEventListener('error', () => {
setNewBannerProgress(null);
showAlert('Erro', 'Erro de conexão ao enviar folder.', 'error');
});
xhr.open('POST', '/api/prematricula/upload');
xhr.send(formData);
}} }}
/> />
</label> </label>
@ -852,6 +893,20 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
</button> </button>
</div> </div>
</div> </div>
) : editBannerProgress !== null ? (
<div className="border border-slate-200 rounded-xl p-5 bg-slate-50 flex flex-col items-center justify-center space-y-3 shadow-inner h-32">
<RefreshCw size={20} className="animate-spin text-indigo-600" />
<div className="text-center">
<p className="text-[10px] font-bold text-slate-700">Enviando e compactando folder...</p>
<p className="text-[9px] text-slate-400 font-bold mt-0.5">{editBannerProgress}% concluído</p>
</div>
<div className="w-full bg-slate-200 rounded-full h-1.5 overflow-hidden shadow-inner">
<div
className="bg-indigo-600 h-full rounded-full transition-all duration-300 ease-out"
style={{ width: `${editBannerProgress}%` }}
></div>
</div>
</div>
) : ( ) : (
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-slate-200 border-dashed rounded-xl cursor-pointer bg-slate-50 hover:bg-slate-100 border-slate-300 transition-all"> <label className="flex flex-col items-center justify-center w-full h-32 border-2 border-slate-200 border-dashed rounded-xl cursor-pointer bg-slate-50 hover:bg-slate-100 border-slate-300 transition-all">
<div className="flex flex-col items-center justify-center pt-3 pb-3"> <div className="flex flex-col items-center justify-center pt-3 pb-3">
@ -867,13 +922,38 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
if (!file) return; if (!file) return;
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
try {
const res = await fetch('/api/prematricula/upload', { method: 'POST', body: formData }); setEditBannerProgress(0);
if (res.ok) { const xhr = new XMLHttpRequest();
const result = await res.json();
setEditingCampo(prev => prev ? { ...prev, placeholder: result.url } : null); xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
setEditBannerProgress(percent);
} }
} catch (err) { console.error(err); } });
xhr.addEventListener('load', () => {
setEditBannerProgress(null);
if (xhr.status >= 200 && xhr.status < 300) {
try {
const result = JSON.parse(xhr.responseText);
setEditingCampo(prev => prev ? { ...prev, placeholder: result.url } : null);
} catch (err) {
showAlert('Erro', 'Falha ao processar resposta do servidor.', 'error');
}
} else {
showAlert('Erro', 'Falha ao carregar folder.', 'error');
}
});
xhr.addEventListener('error', () => {
setEditBannerProgress(null);
showAlert('Erro', 'Erro de conexão ao enviar folder.', 'error');
});
xhr.open('POST', '/api/prematricula/upload');
xhr.send(formData);
}} }}
/> />
</label> </label>

View File

@ -4265,7 +4265,7 @@ async function startServer() {
if (contentType.startsWith('image/')) { if (contentType.startsWith('image/')) {
try { try {
fileBuffer = await sharp(req.file.buffer) fileBuffer = await sharp(req.file.buffer)
.resize(600, 600, { fit: 'inside', withoutEnlargement: true }) .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 75, progressive: true }) .jpeg({ quality: 75, progressive: true })
.toBuffer(); .toBuffer();
contentType = 'image/jpeg'; contentType = 'image/jpeg';