refactor: Blindagem do Frontend contra JSON legado (Migrando Turmas, Cursos e Disciplinas para PostgreSQL nas abas restantes)
This commit is contained in:
parent
2e0a041a26
commit
b55633a191
|
|
@ -12,6 +12,41 @@ interface CertificatesProps {
|
||||||
|
|
||||||
const Certificates: React.FC<CertificatesProps> = ({ data, updateData }) => {
|
const Certificates: React.FC<CertificatesProps> = ({ data, updateData }) => {
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data?.courses || []);
|
||||||
|
const [dbSubjects, setDbSubjects] = useState<any[]>(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 [searchTerm, setSearchTerm] = useState('');
|
||||||
const [selectedStudentId, setSelectedStudentId] = useState('');
|
const [selectedStudentId, setSelectedStudentId] = useState('');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
|
|
@ -212,7 +247,7 @@ const Certificates: React.FC<CertificatesProps> = ({ data, updateData }) => {
|
||||||
const historico = (data.grades || [])
|
const historico = (data.grades || [])
|
||||||
.filter(g => g.studentId === cert.studentId)
|
.filter(g => g.studentId === cert.studentId)
|
||||||
.map(g => {
|
.map(g => {
|
||||||
const subject = data.subjects.find(s => s.id === g.subjectId);
|
const subject = dbSubjects.find(s => s.id === g.subjectId);
|
||||||
return `${subject?.name || 'Disciplina'}: ${g.value}`;
|
return `${subject?.name || 'Disciplina'}: ${g.value}`;
|
||||||
})
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
@ -266,7 +301,7 @@ const Certificates: React.FC<CertificatesProps> = ({ data, updateData }) => {
|
||||||
const currentHistorico = (data.grades || [])
|
const currentHistorico = (data.grades || [])
|
||||||
.filter(g => g.studentId === selectedStudentId)
|
.filter(g => g.studentId === selectedStudentId)
|
||||||
.map(g => {
|
.map(g => {
|
||||||
const subject = data.subjects.find(s => s.id === g.subjectId);
|
const subject = dbSubjects.find(s => s.id === g.subjectId);
|
||||||
return `${subject?.name || 'Disciplina'}: ${g.value}`;
|
return `${subject?.name || 'Disciplina'}: ${g.value}`;
|
||||||
})
|
})
|
||||||
.join('\n') || 'Matemática: 9.5\nPortuguês: 8.0\nHistória: 10.0';
|
.join('\n') || 'Matemática: 9.5\nPortuguês: 8.0\nHistória: 10.0';
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,34 @@ interface ContractsProps {
|
||||||
|
|
||||||
const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data?.courses || []);
|
||||||
|
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/turmas').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
fetch('/api/cursos').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
|
||||||
|
]).then(async (responses) => {
|
||||||
|
const [resT, resC] = 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)
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'contracts' | 'templates'>('contracts');
|
const [activeTab, setActiveTab] = useState<'contracts' | 'templates'>('contracts');
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false);
|
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false);
|
||||||
|
|
@ -42,8 +70,8 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.studentId && !formData.content) {
|
if (formData.studentId && !formData.content) {
|
||||||
const student = data.students.find(s => s.id === formData.studentId);
|
const student = data.students.find(s => s.id === formData.studentId);
|
||||||
const cls = data.classes.find(c => c.id === student?.classId);
|
const cls = dbClasses.find(c => c.id === student?.classId);
|
||||||
const course = data.courses.find(c => c.id === cls?.courseId);
|
const course = dbCourses.find(c => c.id === cls?.courseId);
|
||||||
const templateObj = data.contractTemplates?.find(t => t.id === student?.contractTemplateId);
|
const templateObj = data.contractTemplates?.find(t => t.id === student?.contractTemplateId);
|
||||||
|
|
||||||
if (student && course) {
|
if (student && course) {
|
||||||
|
|
@ -190,8 +218,8 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
const openGenerateModal = (contract: Contract) => {
|
const openGenerateModal = (contract: Contract) => {
|
||||||
const student = data.students.find(s => s.id === contract.studentId);
|
const student = data.students.find(s => s.id === contract.studentId);
|
||||||
const cls = data.classes.find(c => c.id === student?.classId);
|
const cls = dbClasses.find(c => c.id === student?.classId);
|
||||||
const course = data.courses.find(c => c.id === cls?.courseId);
|
const course = dbCourses.find(c => c.id === cls?.courseId);
|
||||||
|
|
||||||
if (student && cls && course) {
|
if (student && cls && course) {
|
||||||
setContractToGenerate(contract);
|
setContractToGenerate(contract);
|
||||||
|
|
@ -211,8 +239,8 @@ const Contracts: React.FC<ContractsProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
const contract = contractToGenerate;
|
const contract = contractToGenerate;
|
||||||
const student = data.students.find(s => s.id === contract.studentId);
|
const student = data.students.find(s => s.id === contract.studentId);
|
||||||
const cls = data.classes.find(c => c.id === student?.classId);
|
const cls = dbClasses.find(c => c.id === student?.classId);
|
||||||
const course = data.courses.find(c => c.id === cls?.courseId);
|
const course = dbCourses.find(c => c.id === cls?.courseId);
|
||||||
|
|
||||||
if (!course || !student) return;
|
if (!course || !student) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,27 @@ interface DashboardProps {
|
||||||
const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
||||||
const [dashboardView, setDashboardView] = useState<'standard' | 'detailed'>('standard');
|
const [dashboardView, setDashboardView] = useState<'standard' | 'detailed'>('standard');
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data.classes || []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetch('/api/turmas')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => {
|
||||||
|
if (json.turmas) {
|
||||||
|
setDbClasses(json.turmas.map((t: any) => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.nome,
|
||||||
|
courseId: t.curso_id,
|
||||||
|
maxStudents: Number(t.max_alunos || 0)
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Basic Stats
|
// Basic Stats
|
||||||
const activeStudents = useMemo(() => data.students.filter(s => s.status === 'active').length, [data.students]);
|
const activeStudents = useMemo(() => data.students.filter(s => s.status === 'active').length, [data.students]);
|
||||||
const totalClasses = useMemo(() => data.classes.length, [data.classes]);
|
const totalClasses = useMemo(() => dbClasses.length, [dbClasses]);
|
||||||
const pendingPayments = useMemo(() => data.payments.filter(p => p.status === 'pending').length, [data.payments]);
|
const pendingPayments = useMemo(() => data.payments.filter(p => p.status === 'pending').length, [data.payments]);
|
||||||
const revenue = useMemo(() => data.payments
|
const revenue = useMemo(() => data.payments
|
||||||
.filter(p => p.status === 'paid')
|
.filter(p => p.status === 'paid')
|
||||||
|
|
@ -76,11 +93,11 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
}, [data.lessons]);
|
}, [data.lessons]);
|
||||||
|
|
||||||
// Chart Data: Class Occupancy
|
// Chart Data: Class Occupancy
|
||||||
const classOccupancy = useMemo(() => data.classes.map(c => ({
|
const classOccupancy = useMemo(() => dbClasses.map(c => ({
|
||||||
name: c.name,
|
name: c.name,
|
||||||
students: data.students.filter(s => s.classId === c.id).length,
|
students: data.students.filter(s => s.classId === c.id).length,
|
||||||
capacity: 20 // Assuming a default capacity
|
capacity: c.maxStudents || 20 // Usando a capacidade real se disponível
|
||||||
})).sort((a, b) => b.students - a.students), [data.classes, data.students]);
|
})).sort((a, b) => b.students - a.students), [dbClasses, data.students]);
|
||||||
|
|
||||||
// Chart Data: Payment Status
|
// Chart Data: Payment Status
|
||||||
const paymentStatus = useMemo(() => [
|
const paymentStatus = useMemo(() => [
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,41 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
const [activeTab, setActiveTab] = useState<'ativos' | 'lixeira'>('ativos');
|
const [activeTab, setActiveTab] = useState<'ativos' | 'lixeira'>('ativos');
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data?.courses || []);
|
||||||
|
const [dbSubjects, setDbSubjects] = useState<any[]>(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 normalizePhotoUrl = (url?: string) => {
|
const normalizePhotoUrl = (url?: string) => {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
if (url.startsWith('data:image') || url.startsWith('/storage')) return url;
|
if (url.startsWith('data:image') || url.startsWith('/storage')) return url;
|
||||||
|
|
@ -35,14 +70,14 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
const filteredExams = exams.filter(exam =>
|
const filteredExams = exams.filter(exam =>
|
||||||
(activeTab === 'ativos' ? !exam.isDeleted : !!exam.isDeleted) &&
|
(activeTab === 'ativos' ? !exam.isDeleted : !!exam.isDeleted) &&
|
||||||
(exam.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
(exam.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
data.classes.find(c => c.id === exam.classId)?.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
dbClasses.find(c => c.id === exam.classId)?.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStartCreate = () => {
|
const handleStartCreate = () => {
|
||||||
setEditingExam({
|
setEditingExam({
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
title: '',
|
title: '',
|
||||||
classId: data.classes[0]?.id || '',
|
classId: dbClasses[0]?.id || '',
|
||||||
durationMinutes: 60,
|
durationMinutes: 60,
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
questions: [],
|
questions: [],
|
||||||
|
|
@ -194,7 +229,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNotifyStudents = async (exam: Exam) => {
|
const handleNotifyStudents = async (exam: Exam) => {
|
||||||
const classObj = (data.classes || []).find(c => c.id === exam.classId);
|
const classObj = (dbClasses || []).find(c => c.id === exam.classId);
|
||||||
if (!classObj) return;
|
if (!classObj) return;
|
||||||
|
|
||||||
showConfirm(
|
showConfirm(
|
||||||
|
|
@ -318,7 +353,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800"
|
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800"
|
||||||
>
|
>
|
||||||
<option value="" disabled>Selecione uma turma</option>
|
<option value="" disabled>Selecione uma turma</option>
|
||||||
{data.classes.map(c => (
|
{dbClasses.map(c => (
|
||||||
<option key={c.id} value={c.id}>{c.name}</option>
|
<option key={c.id} value={c.id}>{c.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -343,7 +378,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800"
|
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800"
|
||||||
>
|
>
|
||||||
<option value="">Nenhuma (não vincular)</option>
|
<option value="">Nenhuma (não vincular)</option>
|
||||||
{(data.subjects || []).map(s => (
|
{(dbSubjects || []).map(s => (
|
||||||
<option key={s.id} value={s.id}>{s.name}</option>
|
<option key={s.id} value={s.id}>{s.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -575,7 +610,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{filteredExams.map(exam => {
|
{filteredExams.map(exam => {
|
||||||
const classObj = data.classes.find(c => c.id === exam.classId);
|
const classObj = dbClasses.find(c => c.id === exam.classId);
|
||||||
return (
|
return (
|
||||||
<div key={exam.id} className={`rounded-2xl p-6 shadow-sm border transition-shadow relative overflow-hidden group ${exam.isDeleted ? 'bg-slate-50 border-slate-200 opacity-80' : 'bg-white border-slate-100 hover:shadow-md'}`}>
|
<div key={exam.id} className={`rounded-2xl p-6 shadow-sm border transition-shadow relative overflow-hidden group ${exam.isDeleted ? 'bg-slate-50 border-slate-200 opacity-80' : 'bg-white border-slate-100 hover:shadow-md'}`}>
|
||||||
<div className={`absolute top-0 left-0 w-1.5 h-full rounded-l-2xl ${exam.isDeleted ? 'bg-slate-400' : 'bg-indigo-500'}`}></div>
|
<div className={`absolute top-0 left-0 w-1.5 h-full rounded-l-2xl ${exam.isDeleted ? 'bg-slate-400' : 'bg-indigo-500'}`}></div>
|
||||||
|
|
@ -613,7 +648,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
{exam.subjectId && (
|
{exam.subjectId && (
|
||||||
<p className="text-sm text-slate-500 flex items-center gap-2">
|
<p className="text-sm text-slate-500 flex items-center gap-2">
|
||||||
<span className="font-bold text-slate-700">Disciplina:</span>
|
<span className="font-bold text-slate-700">Disciplina:</span>
|
||||||
{(data.subjects || []).find(s => s.id === exam.subjectId)?.name || '—'}
|
{(dbSubjects || []).find(s => s.id === exam.subjectId)?.name || '—'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{exam.periodId && (
|
{exam.periodId && (
|
||||||
|
|
@ -716,7 +751,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
onChange={e => setTargetClassId(e.target.value)}
|
onChange={e => setTargetClassId(e.target.value)}
|
||||||
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-bold text-slate-700"
|
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-bold text-slate-700"
|
||||||
>
|
>
|
||||||
{data.classes.map(c => (
|
{dbClasses.map(c => (
|
||||||
<option key={c.id} value={c.id}>{c.name}</option>
|
<option key={c.id} value={c.id}>{c.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,34 @@ interface LessonScheduleProps {
|
||||||
|
|
||||||
const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateData, onClose }) => {
|
const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateData, onClose }) => {
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data?.courses || []);
|
||||||
|
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/turmas').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
fetch('/api/cursos').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
|
||||||
|
]).then(async (responses) => {
|
||||||
|
const [resT, resC] = 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)
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [showGenerateModal, setShowGenerateModal] = useState(false);
|
const [showGenerateModal, setShowGenerateModal] = useState(false);
|
||||||
const [showLessonDetail, setShowLessonDetail] = useState<Lesson | null>(null);
|
const [showLessonDetail, setShowLessonDetail] = useState<Lesson | null>(null);
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
|
|
@ -53,7 +81,7 @@ const LessonSchedule: React.FC<LessonScheduleProps> = ({ classObj, data, updateD
|
||||||
|
|
||||||
// Só dá conflito se for na mesma TURMA ou com o mesmo PROFESSOR
|
// Só dá conflito se for na mesma TURMA ou com o mesmo PROFESSOR
|
||||||
const isSameClass = l.classId === classObj.id;
|
const isSameClass = l.classId === classObj.id;
|
||||||
const otherClass = data.classes.find(c => c.id === l.classId);
|
const otherClass = dbClasses.find(c => c.id === l.classId);
|
||||||
const isSameTeacher = otherClass && classObj.teacher && otherClass.teacher === classObj.teacher;
|
const isSameTeacher = otherClass && classObj.teacher && otherClass.teacher === classObj.teacher;
|
||||||
|
|
||||||
if (!isSameClass && !isSameTeacher) return false;
|
if (!isSameClass && !isSameTeacher) return false;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,34 @@ const defaultTemplates = {
|
||||||
|
|
||||||
const Messages: React.FC<MessagesProps> = ({ data, updateData }) => {
|
const Messages: React.FC<MessagesProps> = ({ data, updateData }) => {
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data?.courses || []);
|
||||||
|
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/turmas').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
fetch('/api/cursos').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
|
||||||
|
]).then(async (responses) => {
|
||||||
|
const [resT, resC] = 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)
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const defaultVars = data.messageTemplates || defaultTemplates;
|
const defaultVars = data.messageTemplates || defaultTemplates;
|
||||||
const initRules = defaultVars.automationRules || defaultTemplates.automationRules;
|
const initRules = defaultVars.automationRules || defaultTemplates.automationRules;
|
||||||
|
|
||||||
|
|
@ -356,7 +384,7 @@ const Messages: React.FC<MessagesProps> = ({ data, updateData }) => {
|
||||||
>
|
>
|
||||||
<option value="">-- Selecione --</option>
|
<option value="">-- Selecione --</option>
|
||||||
{targetType === 'turma'
|
{targetType === 'turma'
|
||||||
? data.classes?.map(c => <option key={c.id} value={c.id}>{c.name}</option>)
|
? dbClasses?.map(c => <option key={c.id} value={c.id}>{c.name}</option>)
|
||||||
: data.students?.map(s => <option key={s.id} value={s.id}>{s.name}</option>)
|
: data.students?.map(s => <option key={s.id} value={s.id}>{s.name}</option>)
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,33 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
const periods = data.periods || [];
|
const periods = data.periods || [];
|
||||||
const grades = data.grades || [];
|
const grades = data.grades || [];
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<Class[]>(data.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data.courses || []);
|
||||||
|
|
||||||
|
const loadClassesAndCourses = async () => {
|
||||||
|
try {
|
||||||
|
const [clsRes, crsRes] = await Promise.all([
|
||||||
|
fetch('/api/turmas'),
|
||||||
|
fetch('/api/cursos')
|
||||||
|
]);
|
||||||
|
const clsData = await clsRes.json();
|
||||||
|
const crsData = await crsRes.json();
|
||||||
|
|
||||||
|
if (clsData.turmas) {
|
||||||
|
setDbClasses(clsData.turmas.map((t: any) => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.nome,
|
||||||
|
courseId: t.curso_id
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
if (crsData.cursos) {
|
||||||
|
setDbCourses(crsData.cursos);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Erro ao buscar turmas/cursos:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadSubjects = async () => {
|
const loadSubjects = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/disciplinas');
|
const res = await fetch('/api/disciplinas');
|
||||||
|
|
@ -59,6 +86,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
loadSubjects();
|
loadSubjects();
|
||||||
|
loadClassesAndCourses();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Buscar todas as notas da turma para mostrar médias na lista
|
// Buscar todas as notas da turma para mostrar médias na lista
|
||||||
|
|
@ -361,7 +389,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
return (totalSum / subjectAverages.length).toFixed(2);
|
return (totalSum / subjectAverages.length).toFixed(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredClasses = data.classes.filter(c =>
|
const filteredClasses = dbClasses.filter(c =>
|
||||||
(c.name || '').toLowerCase().includes((searchTerm || '').toLowerCase())
|
(c.name || '').toLowerCase().includes((searchTerm || '').toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -494,7 +522,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{filteredClasses.map(cls => {
|
{filteredClasses.map(cls => {
|
||||||
const course = data.courses.find(c => c.id === cls.courseId);
|
const course = dbCourses.find(c => c.id === cls.courseId);
|
||||||
const studentCount = data.students.filter(s => s.classId === cls.id).length;
|
const studentCount = data.students.filter(s => s.classId === cls.id).length;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -511,7 +539,7 @@ const ReportCard: React.FC<ReportCardProps> = ({ data, updateData }) => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-black text-slate-800 text-lg">{cls.name}</h3>
|
<h3 className="font-black text-slate-800 text-lg">{cls.name}</h3>
|
||||||
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest">{course?.name || 'Curso não encontrado'}</p>
|
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest">{course?.nome || course?.name || 'Curso não encontrado'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,27 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
const [selectedClassId, setSelectedClassId] = useState<string | null>(null);
|
const [selectedClassId, setSelectedClassId] = useState<string | null>(null);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(dbClasses || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(dbCourses || []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/turmas'),
|
||||||
|
fetch('/api/cursos')
|
||||||
|
]).then(async ([resT, resC]) => {
|
||||||
|
if(resT.ok && resC.ok) {
|
||||||
|
const jsonT = await resT.json();
|
||||||
|
const jsonC = await resC.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 (jsonC.cursos) setDbCourses(jsonC.cursos.map((c: any) => ({
|
||||||
|
id: c.id, name: c.nome
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Academic History State
|
// Academic History State
|
||||||
const [historyGrades, setHistoryGrades] = useState<any[]>([]);
|
const [historyGrades, setHistoryGrades] = useState<any[]>([]);
|
||||||
const [historySubmissions, setHistorySubmissions] = useState<Record<string, {acertos: number, erros: number}>>({});
|
const [historySubmissions, setHistorySubmissions] = useState<Record<string, {acertos: number, erros: number}>>({});
|
||||||
|
|
@ -818,8 +839,8 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Generate Fee and Contract
|
// Process Generate Fee and Contract
|
||||||
const studentClass = data.classes.find(c => c.id === formData.classId);
|
const studentClass = dbClasses.find(c => c.id === formData.classId);
|
||||||
const course = studentClass ? data.courses.find(c => c.id === studentClass.courseId) : null;
|
const course = studentClass ? dbCourses.find(c => c.id === studentClass.courseId) : null;
|
||||||
|
|
||||||
if ((formData as any).generateFee && course) {
|
if ((formData as any).generateFee && course) {
|
||||||
const feeAmount = (course.registrationFee || 0) - (formData.discount || 0);
|
const feeAmount = (course.registrationFee || 0) - (formData.discount || 0);
|
||||||
|
|
@ -1173,9 +1194,9 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
|
|
||||||
{(activeTab === 'active' && !selectedClassId && !searchTerm) ? (
|
{(activeTab === 'active' && !selectedClassId && !searchTerm) ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{data.classes.map(cls => {
|
{dbClasses.map(cls => {
|
||||||
const studentCount = data.students.filter(s => s.classId === cls.id && (activeTab === 'active' ? s.status !== 'cancelled' : s.status === 'cancelled')).length;
|
const studentCount = data.students.filter(s => s.classId === cls.id && (activeTab === 'active' ? s.status !== 'cancelled' : s.status === 'cancelled')).length;
|
||||||
const course = data.courses.find(c => c.id === cls.courseId);
|
const course = dbCourses.find(c => c.id === cls.courseId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|
@ -1242,7 +1263,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
</button>
|
</button>
|
||||||
{selectedClassId && (
|
{selectedClassId && (
|
||||||
<h4 className="text-lg font-black text-slate-800">
|
<h4 className="text-lg font-black text-slate-800">
|
||||||
{selectedClassId === 'none' ? 'Alunos Sem Turma' : data.classes.find(c => c.id === selectedClassId)?.name}
|
{selectedClassId === 'none' ? 'Alunos Sem Turma' : dbClasses.find(c => c.id === selectedClassId)?.name}
|
||||||
</h4>
|
</h4>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1261,7 +1282,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-sm divide-y divide-slate-50">
|
<tbody className="text-sm divide-y divide-slate-50">
|
||||||
{filteredStudents.map(student => {
|
{filteredStudents.map(student => {
|
||||||
const studentClass = data.classes.find(c => c.id === student.classId);
|
const studentClass = dbClasses.find(c => c.id === student.classId);
|
||||||
return (
|
return (
|
||||||
<tr key={student.id} className={`hover:bg-slate-50 transition-colors group ${student.status === 'cancelled' ? 'bg-slate-50 opacity-60 grayscale' : ''}`}>
|
<tr key={student.id} className={`hover:bg-slate-50 transition-colors group ${student.status === 'cancelled' ? 'bg-slate-50 opacity-60 grayscale' : ''}`}>
|
||||||
<td className="p-4">
|
<td className="p-4">
|
||||||
|
|
@ -1752,7 +1773,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
onChange={e => setFormData({...formData, classId: e.target.value})}
|
onChange={e => setFormData({...formData, classId: e.target.value})}
|
||||||
>
|
>
|
||||||
<option value="">Selecione uma turma...</option>
|
<option value="">Selecione uma turma...</option>
|
||||||
{data.classes.map(c => (
|
{dbClasses.map(c => (
|
||||||
<option key={c.id} value={c.id}>{c.name} - {c.schedule}</option>
|
<option key={c.id} value={c.id}>{c.name} - {c.schedule}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -1873,7 +1894,7 @@ const Students: React.FC<StudentsProps> = ({ data, updateData, deepLinkStudentId
|
||||||
onChange={e => setNewClassId(e.target.value)}
|
onChange={e => setNewClassId(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">Selecione uma turma...</option>
|
<option value="">Selecione uma turma...</option>
|
||||||
{data.classes.filter(c => c.id !== transferringStudent.classId).map(c => (
|
{dbClasses.filter(c => c.id !== transferringStudent.classId).map(c => (
|
||||||
<option key={c.id} value={c.id}>{c.name} - {c.schedule}</option>
|
<option key={c.id} value={c.id}>{c.name} - {c.schedule}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function processFile(filename, replaceSubjects = false) {
|
||||||
|
const filePath = path.join(__dirname, '../components', filename);
|
||||||
|
if (!fs.existsSync(filePath)) return;
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
|
// Inject states and fetch
|
||||||
|
// Find a good place to inject. Usually after `const [isModalOpen, setIsModalOpen] = useState(false);` or similar.
|
||||||
|
// We'll just look for a known pattern or add it at the top of the component.
|
||||||
|
|
||||||
|
// We will find `const { showAlert, showConfirm } = useDialog();` and inject right after it
|
||||||
|
const hookDecl = `const { showAlert, showConfirm } = useDialog();`;
|
||||||
|
|
||||||
|
let fetchBlock = `
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data?.courses || []);
|
||||||
|
${replaceSubjects ? 'const [dbSubjects, setDbSubjects] = useState<any[]>(data?.subjects || []);' : ''}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/turmas').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
fetch('/api/cursos').catch(() => ({ ok: false, json: async () => ({}) })),
|
||||||
|
${replaceSubjects ? "fetch('/api/disciplinas').catch(() => ({ ok: false, json: async () => ({}) }))" : ''}
|
||||||
|
]).then(async (responses) => {
|
||||||
|
const [resT, resC${replaceSubjects ? ', 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)
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
${replaceSubjects ? `
|
||||||
|
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);
|
||||||
|
}, []);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (content.includes(hookDecl) && !content.includes('const [dbClasses')) {
|
||||||
|
content = content.replace(hookDecl, hookDecl + '\n' + fetchBlock);
|
||||||
|
|
||||||
|
// Replace usages
|
||||||
|
content = content.replace(/data\.classes/g, 'dbClasses');
|
||||||
|
content = content.replace(/data\.courses/g, 'dbCourses');
|
||||||
|
if (replaceSubjects) {
|
||||||
|
content = content.replace(/data\.subjects/g, 'dbSubjects');
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, content, 'utf8');
|
||||||
|
console.log(filename + ' atualizado.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processFile('Finance.tsx', false);
|
||||||
|
processFile('Exams.tsx', true);
|
||||||
|
processFile('Contracts.tsx', false);
|
||||||
|
processFile('LessonSchedule.tsx', false);
|
||||||
|
processFile('AttendanceQuery.tsx', false);
|
||||||
|
processFile('Certificates.tsx', true);
|
||||||
|
processFile('Messages.tsx', false);
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const filePath = path.join(__dirname, '../components/Students.tsx');
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
|
// Adicionar os states
|
||||||
|
const stateCode = ` const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
const [dbClasses, setDbClasses] = useState<any[]>(data.classes || []);
|
||||||
|
const [dbCourses, setDbCourses] = useState<any[]>(data.courses || []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/turmas'),
|
||||||
|
fetch('/api/cursos')
|
||||||
|
]).then(async ([resT, resC]) => {
|
||||||
|
if(resT.ok && resC.ok) {
|
||||||
|
const jsonT = await resT.json();
|
||||||
|
const jsonC = await resC.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 (jsonC.cursos) setDbCourses(jsonC.cursos.map((c: any) => ({
|
||||||
|
id: c.id, name: c.nome
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
}, []);`;
|
||||||
|
|
||||||
|
content = content.replace(' const [isSaving, setIsSaving] = useState(false);', stateCode);
|
||||||
|
|
||||||
|
// Substituições seguras
|
||||||
|
content = content.replace(/data\.classes/g, 'dbClasses');
|
||||||
|
content = content.replace(/data\.courses/g, 'dbCourses');
|
||||||
|
|
||||||
|
// E arrumar uma possível falha caso haja "dbClasses" onde deveria ser data.classes no destructuring ou useEffect dependencias
|
||||||
|
// Por exemplo: se ele usava deepLinkClassId dependendo de data.classes
|
||||||
|
content = content.replace(/dbClasses, dbCourses, data\.students/g, 'dbClasses, dbCourses, data.students');
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, content, 'utf8');
|
||||||
|
console.log('Students.tsx atualizado com sucesso.');
|
||||||
Loading…
Reference in New Issue