Files
camila/patch_turmas.js

298 lines
16 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const serverFile = path.join(__dirname, 'server.js');
let serverCode = fs.readFileSync(serverFile, 'utf8');
// Add tables to initDb
const tablesSql = `
-- Turmas
CREATE TABLE IF NOT EXISTS escola.turmas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
nome VARCHAR(255) NOT NULL,
sala VARCHAR(255),
periodo VARCHAR(100),
prof_titular VARCHAR(255),
prof_aux1 VARCHAR(255),
prof_aux2 VARCHAR(255),
cuidadora VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Alunos
CREATE TABLE IF NOT EXISTS escola.alunos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
turma_id UUID REFERENCES escola.turmas(id) ON DELETE CASCADE,
nome VARCHAR(255) NOT NULL,
apelido VARCHAR(255),
data_nasc DATE,
especial BOOLEAN DEFAULT false,
especial_detalhes TEXT,
pais TEXT,
autorizados TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
if (!serverCode.includes('CREATE TABLE IF NOT EXISTS escola.turmas')) {
serverCode = serverCode.replace(
"CREATE TABLE IF NOT EXISTS escola.estudio_musicas",
tablesSql + "\n CREATE TABLE IF NOT EXISTS escola.estudio_musicas"
);
}
// Add APIs before app.listen
const apiRoutes = `
// ============================================================
// CRUD TURMAS E ALUNOS (MINHA TURMA)
// ============================================================
app.get('/api/turmas', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
try {
const dbRes = await dbPool.query("SELECT * FROM escola.turmas WHERE usuario_id = $1 ORDER BY created_at ASC;", [usuarioId]);
res.json(dbRes.rows);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/turmas', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
try {
const dbRes = await dbPool.query(
"INSERT INTO escola.turmas (usuario_id, nome, sala, periodo, prof_titular, prof_aux1, prof_aux2, cuidadora) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;",
[usuarioId, nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora]
);
res.status(201).json(dbRes.rows[0]);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.put('/api/turmas/:id', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { id } = req.params;
const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
try {
const dbRes = await dbPool.query(
"UPDATE escola.turmas SET nome = $1, sala = $2, periodo = $3, prof_titular = $4, prof_aux1 = $5, prof_aux2 = $6, cuidadora = $7 WHERE id = $8 AND usuario_id = $9 RETURNING *;",
[nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora, id, usuarioId]
);
res.json(dbRes.rows[0]);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.delete('/api/turmas/:id', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { id } = req.params;
try {
await dbPool.query("DELETE FROM escola.turmas WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.get('/api/alunos', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { turma_id } = req.query;
try {
let query = "SELECT * FROM escola.alunos WHERE usuario_id = $1";
let params = [usuarioId];
if (turma_id) {
query += " AND turma_id = $2";
params.push(turma_id);
}
query += " ORDER BY nome ASC;";
const dbRes = await dbPool.query(query, params);
res.json(dbRes.rows);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/alunos', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
let nascDate = data_nasc ? data_nasc : null;
try {
const dbRes = await dbPool.query(
"INSERT INTO escola.alunos (usuario_id, turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;",
[usuarioId, turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados]
);
res.status(201).json(dbRes.rows[0]);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.put('/api/alunos/:id', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { id } = req.params;
const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
let nascDate = data_nasc ? data_nasc : null;
try {
const dbRes = await dbPool.query(
"UPDATE escola.alunos SET turma_id = $1, nome = $2, apelido = $3, data_nasc = $4, especial = $5, especial_detalhes = $6, pais = $7, autorizados = $8 WHERE id = $9 AND usuario_id = $10 RETURNING *;",
[turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados, id, usuarioId]
);
res.json(dbRes.rows[0]);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.delete('/api/alunos/:id', requireAuth, async (req, res) => {
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
const { id } = req.params;
try {
await dbPool.query("DELETE FROM escola.alunos WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});\n\n`;
if (!serverCode.includes('app.get(\'/api/turmas\'')) {
serverCode = serverCode.replace("app.listen(PORT", apiRoutes + "app.listen(PORT");
}
fs.writeFileSync(serverFile, serverCode);
// ==========================================
// Update index.html
// ==========================================
const indexFile = path.join(__dirname, 'public', 'index.html');
let indexCode = fs.readFileSync(indexFile, 'utf8');
const newTurmaConfigHtml = `
<!-- SECÃO 1: TURMAS (Múltiplas turmas) -->
<div id="turmaConfigSection" style="display: flex; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
<!-- Lado Esquerdo: Lista de Turmas -->
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
<h4 style="margin: 0; color: var(--text-primary);">Minhas Turmas</h4>
<button id="btnCreateTurma" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer;">+ Nova Turma</button>
</div>
<div id="turmasList" style="display: flex; flex-direction: column; gap: 8px;">
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando turmas...</div>
</div>
</div>
<!-- Lado Direito: Form Turma -->
<div id="turmaFormContainer" style="flex: 1.5; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px;">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
<h4 id="turmaFormTitle" style="margin: 0; color: var(--text-primary);">Cadastrar Turma</h4>
</div>
<input type="hidden" id="configTurmaId">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div class="settings-group">
<label>Nome da Turma</label>
<input type="text" id="configTurmaNome" placeholder="Ex: Berçário 2" class="obs-input" style="width: 100%;">
</div>
<div class="settings-group">
<label>Sala</label>
<input type="text" id="configTurmaSala" placeholder="Ex: Sala 4" class="obs-input" style="width: 100%;">
</div>
<div class="settings-group">
<label>Período</label>
<select id="configTurmaPeriodo" class="obs-select" style="width: 100%;">
<option value="Integral">Integral</option>
<option value="Matutino">Matutino</option>
<option value="Vespertino">Vespertino</option>
</select>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; border-top: 1px solid var(--border-light); padding-top: 16px;">
<div class="settings-group">
<label>Professora Titular</label>
<input type="text" id="configTurmaProfTitular" placeholder="Sua Titulação e Nome" class="obs-input" style="width: 100%;">
</div>
<div class="settings-group">
<label>Professora Auxiliar 1</label>
<input type="text" id="configTurmaProfAux1" placeholder="Nome da Prof. Auxiliar" class="obs-input" style="width: 100%;">
</div>
<div class="settings-group">
<label>Professora Auxiliar 2</label>
<input type="text" id="configTurmaProfAux2" placeholder="Nome da Prof. Auxiliar (Opcional)" class="obs-input" style="width: 100%;">
</div>
<div class="settings-group">
<label>Cuidadora</label>
<input type="text" id="configTurmaCuidadora" placeholder="Nome da Cuidadora (Opcional)" class="obs-input" style="width: 100%;">
</div>
</div>
<div style="display: flex; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: auto;">
<button id="btnCancelTurma" style="background: none; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 0.9rem;">Cancelar</button>
<button id="btnSaveTurmaConfig" style="background: var(--brand-green); border: none; color: white; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 0.95rem;">Salvar Turma</button>
</div>
</div>
</div>
`;
const newTurmaAlunosHtml = `
<!-- SECÃO 2: ALUNOS DA TURMA -->
<div id="turmaAlunosSection" style="display: none; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
<!-- Lado Esquerdo: Lista de Crianças e Filtro -->
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
<div style="display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h4 style="margin: 0; color: var(--text-primary);">Lista de Crianças</h4>
<button id="btnCreateChild" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s;">+ Nova Criança</button>
</div>
<div class="settings-group" style="margin-bottom: 0;">
<select id="filterAlunosTurma" class="obs-select" style="width: 100%; padding: 6px;">
<option value="">Todas as Turmas</option>
</select>
</div>
</div>
<div id="childrenList" style="display: flex; flex-direction: column; gap: 8px;">
<!-- Carregado via JS -->
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando alunos...</div>
</div>
</div>
<!-- Lado Direito: Formulário de Cadastro/Edição -->
<div id="childFormContainer" style="flex: 1.5; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px;">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
<h4 id="childFormTitle" style="margin: 0; color: var(--text-primary);">Cadastrar Criança</h4>
<button id="btnVoiceRecordChild" style="background: rgba(16, 163, 127, 0.1); border: 1px solid var(--brand-green); color: var(--brand-green); padding: 4px 8px; border-radius: 6px; font-size: 0.8rem; cursor: pointer; display: flex; align-items: center; gap: 4px;">
🎤 Preencher com Voz
</button>
</div>
<input type="hidden" id="childFormId">
<div class="settings-group" style="gap: 4px;">
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Turma do Aluno</label>
<select id="childFormTurma" class="obs-select" style="width: 100%;"></select>
</div>
<div class="settings-group" style="gap: 4px;">
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Nome Completo da Criança</label>
<input type="text" id="childFormNome" placeholder="Ex: João da Silva" class="obs-input" style="width: 100%;">
</div>
`;
// Only replace if we haven't already
if (indexCode.includes('id="turmaConfigSection" style="display: flex; flex-direction: column;')) {
const regexConfig = /<div id="turmaConfigSection".*?<\/div>\s*<\/div>\s*<!-- SECÃO 2: ALUNOS DA TURMA -->/s;
indexCode = indexCode.replace(regexConfig, newTurmaConfigHtml + "\n\n <!-- SECÃO 2: ALUNOS DA TURMA -->");
}
if (!indexCode.includes('id="filterAlunosTurma"')) {
const regexAlunos = /<!-- SECÃO 2: ALUNOS DA TURMA -->.*?<div class="settings-group" style="gap: 4px;">\s*<label.*?Nome Completo da Criança/s;
indexCode = indexCode.replace(regexAlunos, newTurmaAlunosHtml + "\n <div class=\"settings-group\" style=\"gap: 4px;\">\n <label style=\"color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;\">Nome Completo da Criança");
}
fs.writeFileSync(indexFile, indexCode);