Add CodeMirror SQL editor with autocomplete for tables and columns

This commit is contained in:
Lizandro Guarnizo
2026-06-18 10:43:09 -05:00
parent b57d1ba817
commit 181943bf00
2 changed files with 586 additions and 529 deletions
+9
View File
@@ -5,6 +5,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RIPS Manager - {% block title %}Dashboard{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/sql/sql.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/hint/show-hint.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/hint/sql-hint.min.js"></script>
<style>
.CodeMirror { border: 1px solid #d1d5db; border-radius: 0.5rem; font-size: 13px; height: auto; min-height: 200px; }
.CodeMirror-hints { font-size: 12px; }
</style>
<script>
tailwind.config = {
theme: {
+49 -1
View File
@@ -56,7 +56,8 @@
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Consulta SQL</label>
<textarea name="query_text" id="q_text" rows="10" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm font-mono bg-gray-50" placeholder="SELECT ... FROM ... WHERE ..."></textarea>
<textarea name="query_text" id="q_text" rows="10" required class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm font-mono bg-gray-50" placeholder="SELECT ... FROM ... WHERE ..." style="display:none"></textarea>
<div id="sql-editor-wrapper"></div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Descripción</label>
@@ -332,6 +333,7 @@ function usarSql() {
const sql = document.getElementById('builder-sql').value;
if (!sql || sql.startsWith('--')) { showToast('No hay SQL generado', 'warning'); return; }
document.getElementById('q_text').value = sql;
if (sqlEditor) sqlEditor.setValue(sql);
mostrarEditor();
showToast('SQL copiado al editor');
}
@@ -351,6 +353,7 @@ function editQuery(id, name, type, text, desc) {
document.getElementById('q_desc').value = desc;
document.getElementById('btn-cancel').classList.remove('hidden');
document.querySelector('form').action = '/queries/update/' + id;
if (sqlEditor) sqlEditor.setValue(text);
}
function cancelEdit() {
document.getElementById('query_id').value = '';
@@ -359,6 +362,7 @@ function cancelEdit() {
document.getElementById('q_desc').value = '';
document.getElementById('btn-cancel').classList.add('hidden');
document.querySelector('form').action = '/queries/create';
if (sqlEditor) sqlEditor.setValue('');
}
async function listarTablas(btn) {
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Cargando...';
@@ -374,6 +378,50 @@ async function listarTablas(btn) {
} catch(e) { showToast('Error: ' + e.message, 'error'); }
finally { btn.disabled = false; btn.innerHTML = '<i class="fas fa-table mr-1"></i> Ver tablas disponibles'; }
}
let sqlEditor = null;
async function initSqlEditor() {
sqlEditor = CodeMirror(document.getElementById('sql-editor-wrapper'), {
value: document.getElementById('q_text').value,
mode: 'text/x-sql',
lineNumbers: true,
indentWithTabs: true,
smartIndent: true,
lineWrapping: true,
extraKeys: {
'Ctrl-Space': 'autocomplete'
},
hintOptions: {
tables: {},
words: ['SELECT','FROM','WHERE','AND','OR','JOIN','LEFT','RIGHT','INNER','OUTER','ON','AS','IN','NOT','NULL','IS','LIKE','BETWEEN','INTO','VALUES','SET','UPDATE','DELETE','INSERT','CREATE','ALTER','DROP','TABLE','INDEX','VIEW','ORDER','BY','GROUP','HAVING','DISTINCT','COUNT','SUM','AVG','MIN','MAX','BETWEEN','EXISTS','UNION','ALL','ANY','CASE','WHEN','THEN','ELSE','END','CAST','COALESCE','NULLIF','DESC','ASC']
}
});
sqlEditor.on('change', function() {
document.getElementById('q_text').value = sqlEditor.getValue();
});
// Cargar esquema para autocomplete␍
try {
const resp = await fetch('/queries/esquema', { method: 'POST' });
const data = await resp.json();
if (!data.error && data.tablas) {
const tables = {};
data.tablas.forEach(t => {
tables[t.tabla] = t.columnas.map(c => c.nombre);
});
sqlEditor.setOption('hintOptions', { tables });
}
} catch(e) {}
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('form').addEventListener('submit', function() {
document.getElementById('q_text').value = sqlEditor ? sqlEditor.getValue() : document.getElementById('q_text').value;
});
initSqlEditor();
});
async function ejecutarTest(btn, queryId) {
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Probando...';
try {