import React, { useState, useMemo } from 'react'; import { SchoolData, SchoolProfile } from '../types'; import { dbService } from '../services/dbService'; import { Download, Upload, Trash2, Database, School, Camera, FileText, Info, AlertTriangle, X, CheckCircle, AlertCircle, Cloud, HelpCircle, RefreshCw, Plus, User } from 'lucide-react'; import { isSupabaseConfigured, uploadLogo } from '../services/supabase'; import { useDialog } from '../DialogContext'; import imageCompression from 'browser-image-compression'; interface SettingsProps { data: SchoolData; updateData: (newData: Partial) => void; setData: (data: SchoolData) => void; } const Settings: React.FC = ({ data, updateData, setData }) => { const { showAlert, showConfirm } = useDialog(); const [selectedProfileId, setSelectedProfileId] = useState(data.profile.id || 'main-school'); const [profiles, setProfiles] = useState(data.profiles || [data.profile]); const [globalLogo, setGlobalLogo] = useState(data.logo || ''); const currentProfile = profiles.find(p => p.id === selectedProfileId) || profiles[0]; const currentDirector = useMemo(() => { const employees = data.employees || []; const categories = data.employeeCategories || []; return employees.find(e => { const cat = categories.find(c => c.id === e.categoryId); const catName = cat?.name.toLowerCase() || ''; const empName = e.name.toLowerCase(); return catName.includes('diretor') || catName.includes('diretoria') || empName.includes('diretor') || empName.includes('diretoria'); }); }, [data.employees, data.employeeCategories]); const [profileForm, setProfileForm] = useState(currentProfile); const [showEvolutionModal, setShowEvolutionModal] = useState(false); const [evolutionForm, setEvolutionForm] = useState({ apiUrl: data.evolutionConfig?.apiUrl || '', instanceName: data.evolutionConfig?.instanceName || '', apiKey: data.evolutionConfig?.apiKey || '' }); const saveEvolutionConfig = () => { updateData({ evolutionConfig: evolutionForm }); setShowEvolutionModal(false); showAlert('Sucesso', 'Configurações da Evolution API salvas!', 'success'); }; React.useEffect(() => { setProfileForm(currentProfile); }, [selectedProfileId, profiles]); React.useEffect(() => { setGlobalLogo(data.logo || ''); }, [data.logo]); const [activeTab, setActiveTab] = useState<'perfil' | 'monitoramento'>('perfil'); const [apiLogs, setApiLogs] = useState([]); const [systemStats, setSystemStats] = useState(null); React.useEffect(() => { fetch('/api/system-stats') .then(res => res.json()) .then(data => setSystemStats(data)) .catch(err => console.error('Erro ao buscar stats do sistema:', err)); }, []); React.useEffect(() => { if (activeTab === 'monitoramento') { fetch('/api/logs') .then(res => res.json()) .then(data => setApiLogs(data)) .catch(err => console.error('Erro ao buscar logs:', err)); } }, [activeTab]); const validateCNPJ = (cnpj: string) => { cnpj = cnpj.replace(/[^\d]+/g, ''); if (cnpj === '' || cnpj.length !== 14) return false; if (/^(\d)\1+$/.test(cnpj)) return false; let tamanho = cnpj.length - 2; let numeros = cnpj.substring(0, tamanho); let digitos = cnpj.substring(tamanho); let soma = 0; let pos = tamanho - 7; for (let i = tamanho; i >= 1; i--) { soma += parseInt(numeros.charAt(tamanho - i)) * pos--; if (pos < 2) pos = 9; } let resultado = soma % 11 < 2 ? 0 : 11 - (soma % 11); if (resultado !== parseInt(digitos.charAt(0))) return false; tamanho = tamanho + 1; numeros = cnpj.substring(0, tamanho); soma = 0; pos = tamanho - 7; for (let i = tamanho; i >= 1; i--) { soma += parseInt(numeros.charAt(tamanho - i)) * pos--; if (pos < 2) pos = 9; } resultado = soma % 11 < 2 ? 0 : 11 - (soma % 11); if (resultado !== parseInt(digitos.charAt(1))) return false; return true; }; const handleZipChange = async (zip: string) => { const cleanZip = zip.replace(/\D/g, ''); setProfileForm(prev => ({ ...prev, zip: zip.replace(/^(\d{5})(\d)/, '$1-$2').slice(0, 9) })); if (cleanZip.length === 8) { try { const response = await fetch(`https://viacep.com.br/ws/${cleanZip}/json/`); const data = await response.json(); if (!data.erro) { setProfileForm(prev => ({ ...prev, address: data.logradouro, city: data.localidade, state: data.uf })); } } catch (error) { console.error('Erro ao buscar CEP:', error); } } }; const saveProfile = () => { if (!validateCNPJ(profileForm.cnpj)) { showAlert('Erro', 'CNPJ inválido. Por favor, insira um CNPJ verdadeiro.', 'error'); return; } // Check if trying to set as Matriz but another Matriz already exists if (profileForm.type === 'matriz') { const otherMatriz = profiles.find(p => p.type === 'matriz' && p.id !== profileForm.id); if (otherMatriz) { showAlert('Erro', `Já existe uma matriz cadastrada (${otherMatriz.name}). Só é permitida uma matriz.`, 'error'); return; } } const updatedProfiles = profiles.map(p => p.id === profileForm.id ? profileForm : p); const mainProfile = updatedProfiles.find(p => p.type === 'matriz') || updatedProfiles[0]; setProfiles(updatedProfiles); updateData({ profiles: updatedProfiles, profile: mainProfile }); showAlert('Sucesso', 'Configurações salvas com sucesso!', 'success'); }; const addNewInstitution = () => { const newId = `school-${Date.now()}`; const newProfile: SchoolProfile = { id: newId, name: 'Nova Instituição', address: '', city: '', state: '', zip: '', cnpj: '', phone: '', email: '', type: 'filial' }; setProfiles([...profiles, newProfile]); setSelectedProfileId(newId); }; const deleteInstitution = (id: string) => { if (profiles.length <= 1) { showAlert('Erro', 'É necessário ter pelo menos uma instituição cadastrada.', 'error'); return; } const profileToDelete = profiles.find(p => p.id === id); if (profileToDelete?.type === 'matriz') { showAlert('Erro', 'Não é possível excluir a instituição matriz. Altere outra para matriz primeiro.', 'error'); return; } showConfirm( 'Excluir Instituição?', `Tem certeza que deseja excluir a instituição "${profileToDelete?.name}"?`, () => { const updatedProfiles = profiles.filter(p => p.id !== id); setProfiles(updatedProfiles); setSelectedProfileId(updatedProfiles[0].id); updateData({ profiles: updatedProfiles, profile: updatedProfiles[0] }); } ); }; const closeModal = () => { setIsClosing(true); setTimeout(() => { setShowImportModal(false); setIsClosing(false); }, 300); }; const handleLogoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { try { showAlert('Aguarde', 'Fazendo upload e otimizando a logo...', 'info'); // Compression options const options = { maxSizeMB: 0.1, // 100KB maxWidthOrHeight: 500, useWebWorker: true }; const compressedFile = await imageCompression(file, options); const url = await uploadLogo(compressedFile); if (!url) { throw new Error("Falha ao obter a URL da logo após o upload"); } setGlobalLogo(url); updateData({ logo: url }); showAlert('Sucesso', 'Logo atualizada com sucesso!', 'success'); } catch (error) { console.error('Erro ao fazer upload da imagem:', error); showAlert('Erro', 'Falha ao processar e salvar a imagem.', 'error'); } } }; const handleReset = async () => { await dbService.resetData(); window.location.reload(); }; const formatPhone = (value: string) => { return value .replace(/\D/g, '') .replace(/^(\d{2})(\d)/, '($1) $2 ') .replace(/(\d{4})(\d)/, '$1-$2') .slice(0, 16); }; const inputClass = "w-full px-4 py-3 bg-white text-black border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all shadow-sm text-sm"; return (

Configurações

Gerencie o perfil da escola, modelo de contrato e dados.

{activeTab === 'perfil' ? (

Perfil da Instituição

{/* Institution Selector */}
{profiles.map(p => (
{p.id !== selectedProfileId && p.type !== 'matriz' && ( )}
))}
{globalLogo ? ( Logo ) : (
Logo Global
)}

Logo única para todas as unidades

setProfileForm({...profileForm, name: e.target.value})} />
setProfileForm({...profileForm, cnpj: e.target.value})} />
handleZipChange(e.target.value)} />
setProfileForm({...profileForm, address: e.target.value})} />
setProfileForm({...profileForm, city: e.target.value})} />
setProfileForm({...profileForm, state: e.target.value.toUpperCase().slice(0, 2)})} />
setProfileForm({...profileForm, phone: formatPhone(e.target.value)})} maxLength={16} />
setProfileForm({...profileForm, email: e.target.value})} />
{/* POSTGRESQL CARD */}

Banco de Dados

{systemStats ? ( Online ) : ( )}

Tamanho em Disco

{systemStats?.postgres?.dbSize || '--'}

Tabelas SGBD

{systemStats?.postgres?.tableCount || '--'} PostgreSQL

{/* MINIO STORAGE CARD */}

Storage Físico

{systemStats && !systemStats.minio?.error ? ( MinIO ) : ( Backup )}

Uso Total

{systemStats?.minio?.totalSizeMB || '0.00'} MB

Arquivos

{systemStats?.minio?.totalItems || '0'}

{systemStats?.minio?.buckets && systemStats.minio.buckets.length > 0 && (

Buckets Mapeados

{systemStats.minio.buckets.map((b: any, idx: number) => (
{b.name}
{b.items} itens • {b.sizeMB} MB
))}
)}

Dados do System

Evolution API

{data.evolutionConfig?.apiUrl ? (

URL: {data.evolutionConfig.apiUrl}

Instância: {data.evolutionConfig.instanceName}

API Key: ••••••••

) : (

Nenhuma credencial configurada.

)}

Responsável Legal / Diretor

{currentDirector ? (

Nome: {currentDirector.name}

CPF: {currentDirector.cpf}

Este responsável assinará automaticamente os documentos.

) : (

Nenhum diretor localizado. Cadastre um funcionário como Diretor na aba Funcionários.

)}
) : (

Logs de API

{apiLogs.map((log, i) => ( ))} {apiLogs.length === 0 && ( )}
Data Serviço Ação Detalhes
{new Date(log.date).toLocaleString()} {log.service} {log.action} {JSON.stringify(log.details)}
Nenhum log encontrado.
)} {/* Evolution API Modal */} {showEvolutionModal && (

Credenciais Evolution API

setEvolutionForm({...evolutionForm, apiUrl: e.target.value})} className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition-all shadow-sm text-sm" placeholder="https://api.evolution.com" />
setEvolutionForm({...evolutionForm, instanceName: e.target.value})} className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition-all shadow-sm text-sm" placeholder="minha-instancia" />
setEvolutionForm({...evolutionForm, apiKey: e.target.value})} className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition-all shadow-sm text-sm" placeholder="••••••••••••" />
)}
); }; export default Settings;