298 lines
16 KiB
JavaScript
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);
|