-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
+
+
+
@@ -452,6 +439,9 @@ SELECT * FROM users LIMIT 10;">
this.editingColumn = null;
this.primaryKey = null;
this.tableStructure = [];
+ this.filters = [];
+ this.sortColumn = '';
+ this.sortDirection = 'ASC';
this.init();
}
@@ -598,7 +588,10 @@ SELECT * FROM users LIMIT 10;">
const params = new URLSearchParams({
page: this.currentPage,
limit: this.limit,
- search: searchQuery
+ search: searchQuery,
+ filters: JSON.stringify(this.filters),
+ sortColumn: this.sortColumn,
+ sortDirection: this.sortDirection
});
const response = await fetch(`/api/tables/${this.currentTable}/data?${params}`);
@@ -623,7 +616,11 @@ SELECT * FROM users LIMIT 10;">
// Generate headers
const columns = Object.keys(records[0]);
- headers.innerHTML = columns.map(col => `
${col} | `).join('') + '
Действия | ';
+ headers.innerHTML = columns.map(col => {
+ const isSorted = this.sortColumn === col;
+ const arrow = isSorted ? (this.sortDirection === 'ASC' ? '↑' : '↓') : '';
+ return `
${col} ${arrow} | `;
+ }).join('') + '
Действия | ';
// Generate rows
body.innerHTML = records.map(record => {
@@ -647,6 +644,23 @@ SELECT * FROM users LIMIT 10;">
}).join('');
}
+ sortBy(column) {
+ if (this.sortColumn === column) {
+ if (this.sortDirection === 'ASC') {
+ this.sortDirection = 'DESC';
+ } else {
+ // Third click: remove sorting
+ this.sortColumn = '';
+ this.sortDirection = 'ASC';
+ }
+ } else {
+ this.sortColumn = column;
+ this.sortDirection = 'ASC';
+ }
+ this.currentPage = 1;
+ this.loadTableData();
+ }
+
editRecord(pkValue) {
// Find the record by primary key
// Since we have the data, we can find it
@@ -1176,6 +1190,75 @@ SELECT * FROM users LIMIT 10;">
}
}
+ showFiltersModal() {
+ this.renderFilters();
+ document.getElementById('filtersModal').classList.remove('hidden');
+ }
+
+ renderFilters() {
+ const container = document.getElementById('filtersContainer');
+ container.innerHTML = this.filters.map((filter, index) => `
+
+
+
+
+
+
+ `).join('');
+ lucide.createIcons();
+ }
+
+ addFilter() {
+ this.filters.push({ column: '', operator: '=', value: '' });
+ this.renderFilters();
+ }
+
+ updateFilterColumn(index, column) {
+ this.filters[index].column = column;
+ this.renderFilters(); // Re-render to update disabled state
+ }
+
+ updateFilterOperator(index, operator) {
+ this.filters[index].operator = operator;
+ this.renderFilters(); // Re-render to update disabled state
+ }
+
+ updateFilterValue(index, value) {
+ this.filters[index].value = value;
+ }
+
+ removeFilter(index) {
+ this.filters.splice(index, 1);
+ this.renderFilters();
+ }
+
+ clearFilters() {
+ this.filters = [];
+ this.renderFilters();
+ }
+
+ applyFilters() {
+ this.currentPage = 1;
+ this.closeModal('filtersModal');
+ this.loadTableData();
+ }
+
async loadTableStructure() {
try {
const response = await fetch(`/api/tables/${this.currentTable}/structure`);
@@ -1246,7 +1329,7 @@ SELECT * FROM users LIMIT 10;">
// Close modals on outside click
window.onclick = function(event) {
- if (event.target.classList.contains('fixed')) {
+ if (event.target.classList.contains('fixed') && event.target.id !== 'loginScreen') {
event.target.classList.add('hidden');
}
}
diff --git a/server.js b/server.js
index ca471a4..0ecac32 100644
--- a/server.js
+++ b/server.js
@@ -166,20 +166,24 @@ app.get('/api/tables', requireAuth, async (req, res) => {
}
});
-// Get table data with pagination and search
+// Get table data with pagination, search, filters and sort
app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
const { tableName } = req.params;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const search = req.query.search || '';
+ const filters = req.query.filters ? JSON.parse(req.query.filters) : [];
+ const sortColumn = req.query.sortColumn || '';
+ const sortDirection = req.query.sortDirection || 'ASC';
const offset = (page - 1) * limit;
try {
let whereClause = '';
let params = [limit, offset];
+ let paramIndex = 3; // $1=limit, $2=offset, $3+ for conditions
+ // Search
if (search) {
- // Get column names for search
const columnsResult = await pool.query(`
SELECT column_name
FROM information_schema.columns
@@ -188,20 +192,88 @@ app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => {
`, [tableName]);
const columns = columnsResult.rows.map(row => row.column_name);
- const searchConditions = columns.map(col => `CAST("${col}" AS TEXT) ILIKE $${params.length + 1}`).join(' OR ');
+ const searchConditions = columns.map(col => `CAST("${col}" AS TEXT) ILIKE $${paramIndex}`).join(' OR ');
whereClause = `WHERE ${searchConditions}`;
params.push(`%${search}%`);
+ paramIndex++;
+ }
+
+ // Filters
+ if (filters.length > 0) {
+ const filterConditions = filters.map(filter => {
+ const { column, operator, value } = filter;
+ let condition = '';
+
+ switch (operator) {
+ case '=':
+ condition = `"${column}" = $${paramIndex}`;
+ params.push(value);
+ paramIndex++;
+ break;
+ case '!=':
+ condition = `"${column}" != $${paramIndex}`;
+ params.push(value);
+ paramIndex++;
+ break;
+ case '>':
+ condition = `"${column}" > $${paramIndex}`;
+ params.push(value);
+ paramIndex++;
+ break;
+ case '<':
+ condition = `"${column}" < $${paramIndex}`;
+ params.push(value);
+ paramIndex++;
+ break;
+ case '>=':
+ condition = `"${column}" >= $${paramIndex}`;
+ params.push(value);
+ paramIndex++;
+ break;
+ case '<=':
+ condition = `"${column}" <= $${paramIndex}`;
+ params.push(value);
+ paramIndex++;
+ break;
+ case 'LIKE':
+ condition = `"${column}" LIKE $${paramIndex}`;
+ params.push(`%${value}%`);
+ paramIndex++;
+ break;
+ case 'ILIKE':
+ condition = `"${column}" ILIKE $${paramIndex}`;
+ params.push(`%${value}%`);
+ paramIndex++;
+ break;
+ case 'IS NULL':
+ condition = `"${column}" IS NULL`;
+ break;
+ case 'IS NOT NULL':
+ condition = `"${column}" IS NOT NULL`;
+ break;
+ }
+
+ return condition;
+ }).filter(c => c).join(' AND ');
+
+ whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
}
// Get total count
const countResult = await pool.query(`SELECT COUNT(*) as total FROM "${tableName}" ${whereClause}`, params.slice(2));
const total = parseInt(countResult.rows[0].total);
+ // Build ORDER BY
+ let orderBy = 'ORDER BY (SELECT NULL)'; // Default no order
+ if (sortColumn) {
+ orderBy = `ORDER BY "${sortColumn}" ${sortDirection}`;
+ }
+
// Get data
const result = await pool.query(`
SELECT * FROM "${tableName}"
${whereClause}
- ORDER BY (SELECT NULL) -- No specific order, but consistent
+ ${orderBy}
LIMIT $1 OFFSET $2
`, params);