feat: enhance pre-enrollment with direct link, custom allowed classes, and matricular conversion flow
Build and Deploy (Gitea) / build-and-deploy (push) Failing after 1m47s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Failing after 1m47s
Details
This commit is contained in:
parent
a3bfea8120
commit
65de858755
|
|
@ -3,12 +3,13 @@ import { SchoolData, PreMatriculaCampo, PreMatriculaConfig, PreMatriculaInscrica
|
||||||
import {
|
import {
|
||||||
Plus, Trash2, Save, Eye, EyeOff, Download, GripVertical, Link2,
|
Plus, Trash2, Save, Eye, EyeOff, Download, GripVertical, Link2,
|
||||||
ClipboardPen, Copy, Check, ChevronDown, ChevronRight, Users, RefreshCw,
|
ClipboardPen, Copy, Check, ChevronDown, ChevronRight, Users, RefreshCw,
|
||||||
FileText, X, Settings2, ArrowLeft, ExternalLink, AlertCircle
|
FileText, X, Settings2, ArrowLeft, ExternalLink, AlertCircle, UserCheck
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: SchoolData;
|
data: SchoolData;
|
||||||
updateData: (d: Partial<SchoolData>) => void;
|
updateData: (d: Partial<SchoolData>) => void;
|
||||||
|
onConvert?: (preData: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELD_TYPES: Record<string, string> = {
|
const FIELD_TYPES: Record<string, string> = {
|
||||||
|
|
@ -16,7 +17,7 @@ const FIELD_TYPES: Record<string, string> = {
|
||||||
date: 'Data', select: 'Seleção', textarea: 'Texto Longo', number: 'Número'
|
date: 'Data', select: 'Seleção', textarea: 'Texto Longo', number: 'Número'
|
||||||
};
|
};
|
||||||
|
|
||||||
const PreMatricula: React.FC<Props> = ({ data }) => {
|
const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
|
||||||
const [config, setConfig] = useState<PreMatriculaConfig | null>(null);
|
const [config, setConfig] = useState<PreMatriculaConfig | null>(null);
|
||||||
const [campos, setCampos] = useState<PreMatriculaCampo[]>([]);
|
const [campos, setCampos] = useState<PreMatriculaCampo[]>([]);
|
||||||
const [inscricoes, setInscricoes] = useState<PreMatriculaInscricao[]>([]);
|
const [inscricoes, setInscricoes] = useState<PreMatriculaInscricao[]>([]);
|
||||||
|
|
@ -144,7 +145,9 @@ const PreMatricula: React.FC<Props> = ({ data }) => {
|
||||||
|
|
||||||
const copyLink = () => {
|
const copyLink = () => {
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
const url = `${window.location.origin}/pre-matricula/${config.slug}`;
|
const url = config.slug === 'pre-matricula'
|
||||||
|
? `${window.location.origin}/pre-matricula`
|
||||||
|
: `${window.location.origin}/pre-matricula/${config.slug}`;
|
||||||
navigator.clipboard.writeText(url);
|
navigator.clipboard.writeText(url);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
|
@ -241,6 +244,35 @@ const PreMatricula: React.FC<Props> = ({ data }) => {
|
||||||
<span className="text-xs font-mono text-slate-500">{config?.corPrimaria}</span>
|
<span className="text-xs font-mono text-slate-500">{config?.corPrimaria}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-2">Turmas Disponíveis no Formulário</label>
|
||||||
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3 max-h-40 overflow-y-auto space-y-1">
|
||||||
|
{turmas.map(t => {
|
||||||
|
const isChecked = config?.turmasPermitidas?.includes(t.id);
|
||||||
|
return (
|
||||||
|
<label key={t.id} className="flex items-center gap-2 text-xs font-bold text-slate-700 cursor-pointer hover:bg-slate-100 p-1.5 rounded transition-all">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={(e) => {
|
||||||
|
const current = config?.turmasPermitidas || [];
|
||||||
|
const next = e.target.checked
|
||||||
|
? [...current, t.id]
|
||||||
|
: current.filter(id => id !== t.id);
|
||||||
|
setConfig(prev => prev ? { ...prev, turmasPermitidas: next } : prev);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t.nome}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{turmas.length === 0 && (
|
||||||
|
<p className="text-slate-400 text-xs italic">Nenhuma turma cadastrada</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-slate-400 mt-1 font-medium">Se nenhuma for marcada, todas as turmas aparecerão por padrão.</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Mensagem de Sucesso</label>
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Mensagem de Sucesso</label>
|
||||||
<textarea className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none text-sm font-medium resize-none" rows={2}
|
<textarea className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none text-sm font-medium resize-none" rows={2}
|
||||||
|
|
@ -256,8 +288,10 @@ const PreMatricula: React.FC<Props> = ({ data }) => {
|
||||||
{/* Preview Link */}
|
{/* Preview Link */}
|
||||||
<div className="bg-gradient-to-br from-indigo-50 to-violet-50 p-5 rounded-2xl border border-indigo-100">
|
<div className="bg-gradient-to-br from-indigo-50 to-violet-50 p-5 rounded-2xl border border-indigo-100">
|
||||||
<p className="text-[10px] font-black text-indigo-600 uppercase tracking-widest mb-2">Link Público</p>
|
<p className="text-[10px] font-black text-indigo-600 uppercase tracking-widest mb-2">Link Público</p>
|
||||||
<p className="text-xs font-mono text-indigo-800 break-all mb-3">{window.location.origin}/pre-matricula/{config?.slug}</p>
|
<p className="text-xs font-mono text-indigo-800 break-all mb-3">
|
||||||
<a href={`/pre-matricula/${config?.slug}`} target="_blank" rel="noopener noreferrer"
|
{window.location.origin}/pre-matricula{config?.slug === 'pre-matricula' ? '' : `/${config?.slug}`}
|
||||||
|
</p>
|
||||||
|
<a href={config?.slug === 'pre-matricula' ? '/pre-matricula' : `/pre-matricula/${config?.slug}`} target="_blank" rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 text-xs font-bold text-indigo-600 hover:text-indigo-700"
|
className="flex items-center gap-2 text-xs font-bold text-indigo-600 hover:text-indigo-700"
|
||||||
>
|
>
|
||||||
<ExternalLink size={14} /> Abrir Prévia
|
<ExternalLink size={14} /> Abrir Prévia
|
||||||
|
|
@ -423,7 +457,14 @@ const PreMatricula: React.FC<Props> = ({ data }) => {
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-4 text-slate-500 text-xs">{new Date(insc.createdAt).toLocaleDateString('pt-BR')}</td>
|
<td className="p-4 text-slate-500 text-xs">{new Date(insc.createdAt).toLocaleDateString('pt-BR')}</td>
|
||||||
<td className="p-4 text-right">
|
<td className="p-4 text-right flex items-center justify-end gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => onConvert && onConvert(insc)}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 rounded-lg font-bold text-xs transition-colors"
|
||||||
|
title="Converter em Matrícula"
|
||||||
|
>
|
||||||
|
<UserCheck size={14} /> Matricular
|
||||||
|
</button>
|
||||||
<button onClick={() => deleteInscricao(insc.id)} className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors">
|
<button onClick={() => deleteInscricao(insc.id)} className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors">
|
||||||
<Trash2 size={16} />
|
<Trash2 size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@ interface StudentsProps {
|
||||||
updateData: (newData: Partial<SchoolData>) => void;
|
updateData: (newData: Partial<SchoolData>) => void;
|
||||||
deepLinkStudentId?: string | null;
|
deepLinkStudentId?: string | null;
|
||||||
deepLinkClassId?: string | null;
|
deepLinkClassId?: string | null;
|
||||||
|
deepLinkPreMatricula?: any | null;
|
||||||
clearDeepLink?: () => void;
|
clearDeepLink?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId, deepLinkClassId, clearDeepLink }) => {
|
const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId, deepLinkClassId, deepLinkPreMatricula, clearDeepLink }) => {
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
@ -122,7 +123,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
const streamRef = useRef<MediaStream | null>(null);
|
const streamRef = useRef<MediaStream | null>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Process Deep Links (from Classes or Notifications)
|
// Process Deep Links (from Classes, Notifications, or Pre-Matricula)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (deepLinkClassId) {
|
if (deepLinkClassId) {
|
||||||
setSelectedClassId(deepLinkClassId);
|
setSelectedClassId(deepLinkClassId);
|
||||||
|
|
@ -138,7 +139,74 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
}
|
}
|
||||||
if (clearDeepLink) clearDeepLink();
|
if (clearDeepLink) clearDeepLink();
|
||||||
}
|
}
|
||||||
}, [deepLinkStudentId, deepLinkClassId, data.students]);
|
if (deepLinkPreMatricula) {
|
||||||
|
setEditingStudent(null);
|
||||||
|
|
||||||
|
const parsedData = {
|
||||||
|
name: deepLinkPreMatricula.nome || '',
|
||||||
|
email: deepLinkPreMatricula.email || '',
|
||||||
|
phone: deepLinkPreMatricula.telefone || '',
|
||||||
|
birthDate: '',
|
||||||
|
cpf: '',
|
||||||
|
rg: '',
|
||||||
|
rgIssueDate: '',
|
||||||
|
guardianName: '',
|
||||||
|
guardianPhone: '',
|
||||||
|
guardianCpf: '',
|
||||||
|
guardianBirthDate: '',
|
||||||
|
classId: deepLinkPreMatricula.turmaId || '',
|
||||||
|
status: 'active',
|
||||||
|
registrationDate: new Date().toLocaleDateString('en-US', { timeZone: 'America/Sao_Paulo' }).split(',')[0].split('/').map((x,i,a) => i===2?x:x.padStart(2,'0')).join('-'), // Force Brazil timezone YYYY-MM-DD
|
||||||
|
addressZip: '',
|
||||||
|
addressStreet: '',
|
||||||
|
addressNumber: '',
|
||||||
|
addressNeighborhood: '',
|
||||||
|
addressCity: '',
|
||||||
|
addressState: '',
|
||||||
|
discount: 0,
|
||||||
|
hasGuardian: false,
|
||||||
|
contractTemplateId: '',
|
||||||
|
generateFee: false,
|
||||||
|
generateContract: true,
|
||||||
|
sexo: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom formatting for YYYY-MM-DD for registrationDate
|
||||||
|
const now = new Date();
|
||||||
|
const yr = now.toLocaleDateString('pt-BR', { timeZone: 'America/Sao_Paulo', year: 'numeric' });
|
||||||
|
const mt = now.toLocaleDateString('pt-BR', { timeZone: 'America/Sao_Paulo', month: '2-digit' });
|
||||||
|
const dy = now.toLocaleDateString('pt-BR', { timeZone: 'America/Sao_Paulo', day: '2-digit' });
|
||||||
|
parsedData.registrationDate = `${yr}-${mt}-${dy}`;
|
||||||
|
|
||||||
|
// Map dynamic respostas to standard fields
|
||||||
|
const resps = deepLinkPreMatricula.respostas || {};
|
||||||
|
Object.keys(resps).forEach(key => {
|
||||||
|
const val = resps[key];
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
// We need to fetch the field label to map properly, but we can also match key if the key was saved as string.
|
||||||
|
// Usually, the key is the field ID (UUID). Let's do a case-insensitive search if we can match any standard field names.
|
||||||
|
const label = String(key).toLowerCase();
|
||||||
|
|
||||||
|
if (label.includes('nascimento') || label.includes('nasc') || label.includes('data')) {
|
||||||
|
parsedData.birthDate = val;
|
||||||
|
} else if (label.includes('cpf')) {
|
||||||
|
parsedData.cpf = val;
|
||||||
|
} else if (label.includes('rg')) {
|
||||||
|
parsedData.rg = val;
|
||||||
|
} else if (label.includes('sexo') || label.includes('gênero') || label.includes('genero')) {
|
||||||
|
parsedData.sexo = val;
|
||||||
|
} else if (label.includes('mãe') || label.includes('pai') || label.includes('responsável') || label.includes('responsavel')) {
|
||||||
|
parsedData.guardianName = val;
|
||||||
|
parsedData.hasGuardian = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setFormData(parsedData as any);
|
||||||
|
setShowModal(true);
|
||||||
|
if (clearDeepLink) clearDeepLink();
|
||||||
|
}
|
||||||
|
}, [deepLinkStudentId, deepLinkClassId, deepLinkPreMatricula, dbStudents]);
|
||||||
|
|
||||||
// Fetch Academic History when modal opens
|
// Fetch Academic History when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const App = () => {
|
||||||
}, [currentView]);
|
}, [currentView]);
|
||||||
const [deepLinkStudentId, setDeepLinkStudentId] = useState<string | null>(null);
|
const [deepLinkStudentId, setDeepLinkStudentId] = useState<string | null>(null);
|
||||||
const [deepLinkClassId, setDeepLinkClassId] = useState<string | null>(null);
|
const [deepLinkClassId, setDeepLinkClassId] = useState<string | null>(null);
|
||||||
|
const [deepLinkPreMatricula, setDeepLinkPreMatricula] = useState<any | null>(null);
|
||||||
// Initial load from LocalStorage for speed (fallback), then IDB
|
// Initial load from LocalStorage for speed (fallback), then IDB
|
||||||
const [data, setData] = useState<SchoolData>(dbService.getData());
|
const [data, setData] = useState<SchoolData>(dbService.getData());
|
||||||
|
|
||||||
|
|
@ -219,6 +220,11 @@ const App = () => {
|
||||||
setCurrentView(View.Students);
|
setCurrentView(View.Students);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNavigateToStudentsWithPreMatricula = (preData: any) => {
|
||||||
|
setDeepLinkPreMatricula(preData);
|
||||||
|
setCurrentView(View.Students);
|
||||||
|
};
|
||||||
|
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
switch (currentView) {
|
switch (currentView) {
|
||||||
case View.Dashboard:
|
case View.Dashboard:
|
||||||
|
|
@ -226,7 +232,20 @@ const App = () => {
|
||||||
case View.Courses:
|
case View.Courses:
|
||||||
return <Courses data={data} updateData={updateData} />;
|
return <Courses data={data} updateData={updateData} />;
|
||||||
case View.Students:
|
case View.Students:
|
||||||
return <Students data={data} updateData={updateData} deepLinkStudentId={deepLinkStudentId} deepLinkClassId={deepLinkClassId} clearDeepLink={() => { setDeepLinkStudentId(null); setDeepLinkClassId(null); }} />;
|
return (
|
||||||
|
<Students
|
||||||
|
data={data}
|
||||||
|
updateData={updateData}
|
||||||
|
deepLinkStudentId={deepLinkStudentId}
|
||||||
|
deepLinkClassId={deepLinkClassId}
|
||||||
|
deepLinkPreMatricula={deepLinkPreMatricula}
|
||||||
|
clearDeepLink={() => {
|
||||||
|
setDeepLinkStudentId(null);
|
||||||
|
setDeepLinkClassId(null);
|
||||||
|
setDeepLinkPreMatricula(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case View.Classes:
|
case View.Classes:
|
||||||
return <Classes data={data} updateData={updateData} onNavigateToClass={handleNavigateToClass} />;
|
return <Classes data={data} updateData={updateData} onNavigateToClass={handleNavigateToClass} />;
|
||||||
case View.Finance:
|
case View.Finance:
|
||||||
|
|
@ -254,7 +273,7 @@ const App = () => {
|
||||||
case View.Settings:
|
case View.Settings:
|
||||||
return <Settings data={data} updateData={updateData} setData={setData} />;
|
return <Settings data={data} updateData={updateData} setData={setData} />;
|
||||||
case View.PreMatricula:
|
case View.PreMatricula:
|
||||||
return <PreMatricula data={data} updateData={updateData} />;
|
return <PreMatricula data={data} updateData={updateData} onConvert={handleNavigateToStudentsWithPreMatricula} />;
|
||||||
default:
|
default:
|
||||||
return <Dashboard data={data} />;
|
return <Dashboard data={data} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString: 'postgresql://edumanager:EduManager2026!Seguro@150.230.87.131:5432/edumanager'
|
||||||
|
});
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query(`
|
||||||
|
ALTER TABLE prematricula_config
|
||||||
|
ADD COLUMN IF NOT EXISTS turmas_permitidas TEXT[] DEFAULT '{}';
|
||||||
|
`);
|
||||||
|
console.log('✅ Coluna turmas_permitidas adicionada com sucesso!');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ Erro ao adicionar coluna:', e.message);
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
@ -2896,7 +2896,8 @@ async function startServer() {
|
||||||
res.json({ config: {
|
res.json({ config: {
|
||||||
id: r.id, titulo: r.titulo, descricao: r.descricao, slug: r.slug,
|
id: r.id, titulo: r.titulo, descricao: r.descricao, slug: r.slug,
|
||||||
status: r.status, corPrimaria: r.cor_primaria, logoUrl: r.logo_url,
|
status: r.status, corPrimaria: r.cor_primaria, logoUrl: r.logo_url,
|
||||||
mensagemSucesso: r.mensagem_sucesso
|
mensagemSucesso: r.mensagem_sucesso,
|
||||||
|
turmasPermitidas: r.turmas_permitidas || []
|
||||||
}});
|
}});
|
||||||
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
||||||
});
|
});
|
||||||
|
|
@ -2906,8 +2907,8 @@ async function startServer() {
|
||||||
const c = req.body;
|
const c = req.body;
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE prematricula_config SET titulo=$1, descricao=$2, slug=$3, status=$4,
|
`UPDATE prematricula_config SET titulo=$1, descricao=$2, slug=$3, status=$4,
|
||||||
cor_primaria=$5, logo_url=$6, mensagem_sucesso=$7, updated_at=NOW() WHERE id=1`,
|
cor_primaria=$5, logo_url=$6, mensagem_sucesso=$7, turmas_permitidas=$8, updated_at=NOW() WHERE id=1`,
|
||||||
[c.titulo, c.descricao, c.slug, c.status, c.corPrimaria || '#4f46e5', c.logoUrl || '', c.mensagemSucesso || '']
|
[c.titulo, c.descricao, c.slug, c.status, c.corPrimaria || '#4f46e5', c.logoUrl || '', c.mensagemSucesso || '', c.turmasPermitidas || []]
|
||||||
);
|
);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
||||||
|
|
@ -2991,13 +2992,23 @@ async function startServer() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Página pública de pré-matrícula (API que retorna dados do formulário)
|
// Página pública de pré-matrícula (API que retorna dados do formulário)
|
||||||
app.get('/api/prematricula/public/:slug', async (req, res) => {
|
app.get('/api/prematricula/public/:slug?', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { rows: cfgRows } = await pool.query('SELECT * FROM prematricula_config WHERE slug = $1 AND status = $2', [req.params.slug, 'published']);
|
const slug = req.params.slug || 'pre-matricula';
|
||||||
|
const { rows: cfgRows } = await pool.query(
|
||||||
|
'SELECT * FROM prematricula_config WHERE (slug = $1 OR id = 1) AND status = $2 ORDER BY id ASC LIMIT 1',
|
||||||
|
[slug, 'published']
|
||||||
|
);
|
||||||
if (cfgRows.length === 0) return res.status(404).json({ error: 'Formulário não encontrado ou não publicado.' });
|
if (cfgRows.length === 0) return res.status(404).json({ error: 'Formulário não encontrado ou não publicado.' });
|
||||||
const cfg = cfgRows[0];
|
const cfg = cfgRows[0];
|
||||||
const { rows: camposRows } = await pool.query('SELECT * FROM prematricula_campos WHERE ativo = true ORDER BY ordem ASC');
|
const { rows: camposRows } = await pool.query('SELECT * FROM prematricula_campos WHERE ativo = true ORDER BY ordem ASC');
|
||||||
const turmasResult = await pool.query('SELECT id, nome FROM turmas ORDER BY nome ASC');
|
const turmasResult = await pool.query('SELECT id, nome FROM turmas ORDER BY nome ASC');
|
||||||
|
|
||||||
|
let turmasRows = turmasResult.rows;
|
||||||
|
if (cfg.turmas_permitidas && cfg.turmas_permitidas.length > 0) {
|
||||||
|
turmasRows = turmasRows.filter(t => cfg.turmas_permitidas.includes(t.id));
|
||||||
|
}
|
||||||
|
|
||||||
const appData = await getSchoolData();
|
const appData = await getSchoolData();
|
||||||
res.json({
|
res.json({
|
||||||
config: {
|
config: {
|
||||||
|
|
@ -3008,13 +3019,23 @@ async function startServer() {
|
||||||
id: r.id, label: r.label, tipo: r.tipo, placeholder: r.placeholder,
|
id: r.id, label: r.label, tipo: r.tipo, placeholder: r.placeholder,
|
||||||
obrigatorio: r.obrigatorio, opcoes: r.opcoes || []
|
obrigatorio: r.obrigatorio, opcoes: r.opcoes || []
|
||||||
})),
|
})),
|
||||||
turmas: turmasResult.rows.map(t => ({ id: t.id, nome: t.nome })),
|
turmas: turmasRows.map(t => ({ id: t.id, nome: t.nome })),
|
||||||
escola: { nome: appData?.profile?.name || 'EduManager', logo: appData?.logo || '' }
|
escola: { nome: appData?.profile?.name || 'EduManager', logo: appData?.logo || '' }
|
||||||
});
|
});
|
||||||
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rota que serve a página HTML pública do formulário de pré-matrícula
|
// Rota que serve a página HTML pública do formulário de pré-matrícula
|
||||||
|
app.get('/pre-matricula', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { rows } = await pool.query('SELECT slug FROM prematricula_config WHERE id = 1');
|
||||||
|
const slug = rows[0]?.slug || 'pre-matricula';
|
||||||
|
res.send(getPreMatriculaHTML(slug));
|
||||||
|
} catch (e) {
|
||||||
|
res.send(getPreMatriculaHTML('pre-matricula'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/pre-matricula/:slug', (req, res) => {
|
app.get('/pre-matricula/:slug', (req, res) => {
|
||||||
const { slug } = req.params;
|
const { slug } = req.params;
|
||||||
res.send(getPreMatriculaHTML(slug));
|
res.send(getPreMatriculaHTML(slug));
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,7 @@ export interface PreMatriculaConfig {
|
||||||
corPrimaria: string;
|
corPrimaria: string;
|
||||||
logoUrl: string;
|
logoUrl: string;
|
||||||
mensagemSucesso: string;
|
mensagemSucesso: string;
|
||||||
|
turmasPermitidas?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreMatriculaInscricao {
|
export interface PreMatriculaInscricao {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue