import React, { useState, useRef, useEffect } from 'react'; import { SchoolData, Certificate, Student, CertificateTemplate, TextOverlay } from '../types'; import { dbService } from '../services/dbService'; import { useDialog } from '../DialogContext'; import { Award, Upload, Search, Trash2, Download, Eye, X, Image as ImageIcon, Edit2, Save, Type, Move, Palette, Baseline, Layout, Copy, Check, Plus } from 'lucide-react'; import jsPDF from 'jspdf'; interface CertificatesProps { data: SchoolData; updateData: (newData: Partial) => void; } const Certificates: React.FC = ({ data, updateData }) => { const { showAlert, showConfirm } = useDialog(); const [dbClasses, setDbClasses] = useState(data?.classes || []); const [dbCourses, setDbCourses] = useState(data?.courses || []); const [dbSubjects, setDbSubjects] = useState(data?.subjects || []); React.useEffect(() => { Promise.all([ fetch('/api/turmas').catch(() => ({ ok: false, json: async () => ({}) })), fetch('/api/cursos').catch(() => ({ ok: false, json: async () => ({}) })), fetch('/api/disciplinas').catch(() => ({ ok: false, json: async () => ({}) })) ]).then(async (responses) => { const [resT, resC, resS] = responses; if (resT && resT.ok) { const jsonT = await resT.json(); if (jsonT.turmas) setDbClasses(jsonT.turmas.map((t: any) => ({ id: t.id, name: t.nome, courseId: t.curso_id, maxStudents: Number(t.max_alunos || 0) }))); } if (resC && resC.ok) { const jsonC = await resC.json(); if (jsonC.cursos) setDbCourses(jsonC.cursos.map((c: any) => ({ id: c.id, name: c.nome, monthlyFee: Number(c.mensalidade || 0), registrationFee: Number(c.taxa_matricula || 0) }))); } if (resS && resS.ok) { const jsonS = await resS.json(); if (jsonS.disciplinas) setDbSubjects(jsonS.disciplinas.map((d: any) => ({ id: d.id, name: d.nome }))); } }).catch(console.error); }, []); const [searchTerm, setSearchTerm] = useState(''); const [selectedStudentId, setSelectedStudentId] = useState(''); const [description, setDescription] = useState(''); const [frontImage, setFrontImage] = useState(null); const [backImage, setBackImage] = useState(null); const [previewCertificate, setPreviewCertificate] = useState(null); const [editingCertificate, setEditingCertificate] = useState(null); const [activeTab, setActiveTab] = useState<'front' | 'back'>('front'); const [templateName, setTemplateName] = useState(''); const [showTemplateModal, setShowTemplateModal] = useState(false); // Overlays States const [frontOverlays, setFrontOverlays] = useState([]); const [backOverlays, setBackOverlays] = useState([]); const [selectedOverlayId, setSelectedOverlayId] = useState(null); const fileInputFrontRef = useRef(null); const fileInputBackRef = useRef(null); const activeOverlays = activeTab === 'front' ? frontOverlays : backOverlays; const setActiveOverlays = activeTab === 'front' ? setFrontOverlays : setBackOverlays; const selectedOverlay = activeOverlays.find(o => o.id === selectedOverlayId); const handleAddOverlay = () => { const newOverlay: TextOverlay = { id: crypto.randomUUID(), text: activeTab === 'front' ? 'Certificamos que {{aluno}}...' : 'Conteúdo do verso...', x: 50, y: 50, fontSize: activeTab === 'front' ? 24 : 12, color: '#000000' }; setActiveOverlays([...activeOverlays, newOverlay]); setSelectedOverlayId(newOverlay.id); }; const handleUpdateOverlay = (id: string, updates: Partial) => { setActiveOverlays(activeOverlays.map(o => o.id === id ? { ...o, ...updates } : o)); }; const handleRemoveOverlay = (id: string) => { setActiveOverlays(activeOverlays.filter(o => o.id !== id)); if (selectedOverlayId === id) setSelectedOverlayId(null); }; const handleImageUpload = (e: React.ChangeEvent, side: 'front' | 'back') => { const file = e.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { if (side === 'front') setFrontImage(reader.result as string); else setBackImage(reader.result as string); }; reader.readAsDataURL(file); } }; const handleSaveCertificate = () => { if (!selectedStudentId || !frontImage) { showAlert('Atenção', '⚠️ Por favor, selecione um aluno e faça upload da imagem da frente.', 'warning'); return; } const certificateData: Certificate = { id: editingCertificate ? editingCertificate.id : crypto.randomUUID(), studentId: selectedStudentId, description, frontImage, backImage: backImage || undefined, issueDate: editingCertificate ? editingCertificate.issueDate : new Date().toISOString(), frontOverlays, backOverlays }; let updatedCertificates; if (editingCertificate) { updatedCertificates = (data.certificates || []).map(c => c.id === editingCertificate.id ? certificateData : c); } else { updatedCertificates = [...(data.certificates || []), certificateData]; } updateData({ certificates: updatedCertificates }); dbService.saveData({ ...data, certificates: updatedCertificates }); resetForm(); showAlert('Sucesso', editingCertificate ? '✅ Certificado atualizado!' : '✅ Certificado salvo com sucesso!', 'success'); }; const handleSaveTemplate = () => { if (!templateName || !frontImage) { showAlert('Atenção', '⚠️ Informe um nome para o modelo e carregue pelo menos a imagem da frente.', 'warning'); return; } const newTemplate: CertificateTemplate = { id: crypto.randomUUID(), name: templateName, frontImage, backImage: backImage || undefined, frontOverlays, backOverlays }; const updatedTemplates = [...(data.certificateTemplates || []), newTemplate]; updateData({ certificateTemplates: updatedTemplates }); dbService.saveData({ ...data, certificateTemplates: updatedTemplates }); setTemplateName(''); setShowTemplateModal(false); showAlert('Sucesso', '✅ Modelo salvo com sucesso!', 'success'); }; const loadTemplate = (template: CertificateTemplate) => { setFrontImage(template.frontImage); setBackImage(template.backImage); setFrontOverlays(template.frontOverlays || []); setBackOverlays(template.backOverlays || []); setSelectedOverlayId(null); showAlert('Modelo Carregado', `✅ Modelo "${template.name}" carregado!`, 'success'); }; const resetForm = () => { setSelectedStudentId(''); setDescription(''); setFrontImage(null); setBackImage(null); setFrontOverlays([]); setBackOverlays([]); setSelectedOverlayId(null); setEditingCertificate(null); if (fileInputFrontRef.current) fileInputFrontRef.current.value = ''; if (fileInputBackRef.current) fileInputBackRef.current.value = ''; }; const handleEditCertificate = (cert: Certificate) => { setEditingCertificate(cert); setSelectedStudentId(cert.studentId); setDescription(cert.description || ''); setFrontImage(cert.frontImage); setBackImage(cert.backImage); setFrontOverlays(cert.frontOverlays || []); setBackOverlays(cert.backOverlays || []); setSelectedOverlayId(null); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const handleDeleteCertificate = (id: string) => { showConfirm( 'Excluir Certificado', '⚠️ Tem certeza que deseja excluir este certificado?', () => { const updatedCertificates = (data.certificates || []).filter(c => c.id !== id); updateData({ certificates: updatedCertificates }); dbService.saveData({ ...data, certificates: updatedCertificates }); } ); }; const handleDeleteTemplate = (id: string) => { showConfirm( 'Excluir Modelo', '⚠️ Excluir este modelo?', () => { const updatedTemplates = (data.certificateTemplates || []).filter(t => t.id !== id); updateData({ certificateTemplates: updatedTemplates }); dbService.saveData({ ...data, certificateTemplates: updatedTemplates }); } ); }; const getStudentStats = (studentId: string) => { const studentGrades = (data.grades || []).filter(g => g.studentId === studentId); const media = studentGrades.length > 0 ? (studentGrades.reduce((acc, curr) => acc + curr.value, 0) / studentGrades.length).toFixed(1) : '0.0'; const studentAttendance = (data.attendance || []).filter(a => a.studentId === studentId); const presences = studentAttendance.filter(a => a.type === 'presence').length; const frequencia = studentAttendance.length > 0 ? ((presences / studentAttendance.length) * 100).toFixed(0) : '0'; return { media, frequencia }; }; const handleDownloadPDF = (cert: Certificate) => { const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' }); const width = doc.internal.pageSize.getWidth(); const height = doc.internal.pageSize.getHeight(); const student = data.students.find(s => s.id === cert.studentId); const studentName = student?.name || 'Aluno'; const { media, frequencia } = getStudentStats(cert.studentId); const historico = (data.grades || []) .filter(g => g.studentId === cert.studentId) .map(g => { const subject = dbSubjects.find(s => s.id === g.subjectId); return `${subject?.name || 'Disciplina'}: ${g.value}`; }) .join('\n'); // Helper to draw wrapped text const drawWrappedText = (text: string, xPerc: number, yPerc: number, fSize: number, color: string) => { const processedText = text .replace(/{{aluno}}/gi, studentName) .replace(/{{media}}/gi, media) .replace(/{{frequencia}}/gi, `${frequencia}%`) .replace(/{{historico}}/gi, historico); doc.setTextColor(color); doc.setFontSize(fSize); const xPos = (xPerc / 100) * width; const yPos = (yPerc / 100) * height; const maxW = width * 0.8; // 80% of page width const lines = doc.splitTextToSize(processedText, maxW); doc.text(lines, xPos, yPos, { align: 'center' }); }; // Front doc.addImage(cert.frontImage, 'JPEG', 0, 0, width, height); (cert.frontOverlays || []).forEach(o => { drawWrappedText(o.text, o.x, o.y, o.fontSize, o.color); }); // Back (Only if has back image or back overlays) if (cert.backImage || (cert.backOverlays && cert.backOverlays.length > 0)) { doc.addPage(); if (cert.backImage) { doc.addImage(cert.backImage, 'JPEG', 0, 0, width, height); } (cert.backOverlays || []).forEach(o => { drawWrappedText(o.text, o.x, o.y, o.fontSize, o.color); }); } doc.save(`Certificado_${studentName.replace(/\s+/g, '_')}.pdf`); }; const filteredCertificates = (data.certificates || []).filter(cert => { const student = data.students.find(s => s.id === cert.studentId); return (student?.name || '').toLowerCase().includes((searchTerm || '').toLowerCase()); }); const currentStudentName = data.students.find(s => s.id === selectedStudentId)?.name || 'Nome do Aluno'; const { media: currentMedia, frequencia: currentFrequencia } = getStudentStats(selectedStudentId); const currentHistorico = (data.grades || []) .filter(g => g.studentId === selectedStudentId) .map(g => { const subject = dbSubjects.find(s => s.id === g.subjectId); return `${subject?.name || 'Disciplina'}: ${g.value}`; }) .join('\n') || 'Matemática: 9.5\nPortuguês: 8.0\nHistória: 10.0'; const processPreviewText = (text: string) => { return text .replace(/{{aluno}}/gi, currentStudentName) .replace(/{{media}}/gi, currentMedia) .replace(/{{frequencia}}/gi, `${currentFrequencia}%`) .replace(/{{historico}}/gi, currentHistorico); }; return (

Certificados

Gerencie, edite e emita certificados personalizados.

{editingCertificate && ( )}
{/* Form & Preview Section */}

{editingCertificate ? 'Editar Certificado' : 'Novo Certificado'}

setDescription(e.target.value)} />

Textos ({activeTab === 'front' ? 'Frente' : 'Verso'})

{activeOverlays.map((overlay, index) => (
setSelectedOverlayId(overlay.id)} >
Texto #{index + 1}
Variáveis: {"{{aluno}}"}, {"{{media}}"}, {"{{frequencia}}"}, {"{{historico}}"}