mayankjoin3

Dynamic Tables Query and Multi Select

Aug 18th, 2025
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 38.66 KB | None | 0 0
  1. <?php
  2. // PHP Backend Logic
  3. // This script handles all database interactions.
  4. // It is designed to be self-contained within the HTML file.
  5.  
  6. // --- Configuration and Error Reporting ---
  7. // Enable MySQLi exceptions for better error handling.
  8. // This allows us to use try-catch blocks for database operations.
  9. mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
  10.  
  11. // Define a constant for the error log file path
  12. define('LOG_FILE_PATH', 'error_log.txt');
  13.  
  14. // --- Centralized Error Logging Function ---
  15. // This function writes detailed error messages to the log file.
  16. function log_error($message, $file, $line) {
  17.     // OLD: error_log($message);
  18.     // NEW: Log the error with a timestamp and file/line number
  19.     $timestamp = date('d-M-Y H:i:s');
  20.     $log_message = sprintf("[%s] [%s:%s] %s\n", $timestamp, $file, $line, $message);
  21.     error_log($log_message, 3, LOG_FILE_PATH);
  22. }
  23.  
  24. // Database connection details
  25. // IMPORTANT: Replace with your actual credentials
  26. $db_host = "localhost";
  27. $db_user = "root";
  28. $db_pass =  "";
  29. $db_name = "test"; // The database name as specified in the request
  30.  
  31. // Establish a connection to the database
  32. try {
  33.     $mysqli = new mysqli($db_host, $db_user, $db_pass, $db_name);
  34.     // Set character set to prevent encoding issues
  35.     $mysqli->set_charset("utf8");
  36. } catch (mysqli_sql_exception $e) {
  37.     // Log the connection error and provide a user-friendly message
  38.     log_error("Database connection failed: " . $e->getMessage(), __FILE__, __LINE__);
  39.     die("An error occurred while connecting to the database. Please check the logs for more details.");
  40. }
  41.  
  42. // --- Function to safely escape user input ---
  43. // This function trims whitespace and escapes special characters to prevent SQL injection.
  44. function clean_input($data) {
  45.     global $mysqli;
  46.     // OLD: $data = trim($data);
  47.     // NEW: Ensure data is a string before trimming and escaping
  48.     $data = is_string($data) ? trim($data) : $data;
  49.     $data = mysqli_real_escape_string($mysqli, $data);
  50.     return $data;
  51. }
  52.  
  53. // --- AJAX action handler ---
  54. // This part of the code responds to requests from the frontend.
  55. try {
  56.     if (isset($_POST['action'])) {
  57.         $action = clean_input($_POST['action']);
  58.        
  59.         // Action 1: Fetch columns and key info for a selected table
  60.         if ($action === 'get_columns') {
  61.             $table_name = isset($_POST['table']) ? clean_input($_POST['table']) : '';
  62.             if (empty($table_name)) {
  63.                 die(json_encode(['error' => 'No table specified.']));
  64.             }
  65.  
  66.             $response = [
  67.                 'columns' => [],
  68.                 'primary_key' => '',
  69.                 'auto_increment' => ''
  70.             ];
  71.  
  72.             try {
  73.                 // Get column details and primary key
  74.                 $result = $mysqli->query("DESCRIBE `$table_name`");
  75.                 if ($result) {
  76.                     while ($row = $result->fetch_assoc()) {
  77.                         $column_name = $row['Field'];
  78.                         $response['columns'][] = $column_name;
  79.                         if ($row['Key'] === 'PRI') {
  80.                             $response['primary_key'] = $column_name;
  81.                         }
  82.                     }
  83.                     $result->free();
  84.                 }
  85.  
  86.                 // Get auto_increment property from SHOW CREATE TABLE
  87.                 $result = $mysqli->query("SHOW CREATE TABLE `$table_name`");
  88.                 if ($result) {
  89.                     $row = $result->fetch_assoc();
  90.                     $create_table_sql = $row['Create Table'];
  91.                     if (preg_match('/`[^`]+`\s+INT\([^`]+\)\s+NOT NULL\s+AUTO_INCREMENT/', $create_table_sql, $matches)) {
  92.                         $auto_increment_column = str_replace('`', '', substr($matches[0], 0, strpos($matches[0], ' ')));
  93.                         $response['auto_increment'] = $auto_increment_column;
  94.                     }
  95.                     $result->free();
  96.                 }
  97.  
  98.                 header('Content-Type: application/json');
  99.                 echo json_encode($response);
  100.             } catch (mysqli_sql_exception $e) {
  101.                 // Log the query error
  102.                 log_error("Failed to get columns for table '$table_name': " . $e->getMessage(), __FILE__, __LINE__);
  103.                 die(json_encode(['error' => 'An error occurred while fetching table information.']));
  104.             }
  105.             exit;
  106.         }
  107.  
  108.         // Action 2: Execute a user-defined query and display results
  109.         if ($action === 'execute_query') {
  110.             // OLD: $query = isset($_POST['query']) ? trim($_POST['query']) : '';
  111.             // NEW: Apply trim to the raw query
  112.             $query = isset($_POST['query']) ? trim($_POST['query']) : '';
  113.  
  114.             // Note: This feature allows the user to run arbitrary SQL.
  115.             // This is a known risk, and the user must be trusted.
  116.             // Prepared statements are not used here as the query itself is dynamic.
  117.  
  118.             $html = '';
  119.             try {
  120.                 $result = $mysqli->query($query);
  121.                 if ($result === false) {
  122.                     // This case should be caught by the exception, but added as a failsafe
  123.                     throw new mysqli_sql_exception("Query returned false result.");
  124.                 }
  125.  
  126.                 $html .= '<div class="table-responsive mt-3">';
  127.                 $html .= '<table class="table table-striped table-bordered table-hover">';
  128.                 $html .= '<thead class="table-dark"><tr>';
  129.  
  130.                 // Fetch headers
  131.                 $field_info = $result->fetch_fields();
  132.                 $headers = [];
  133.                 foreach ($field_info as $val) {
  134.                     $headers[] = $val->name;
  135.                     $html .= '<th>' . htmlspecialchars($val->name) . '</th>';
  136.                 }
  137.                 $html .= '</tr></thead><tbody>';
  138.  
  139.                 // Fetch and display data
  140.                 $primary_key = isset($_POST['primary_key']) ? trim($_POST['primary_key']) : '';
  141.                 while ($row = $result->fetch_assoc()) {
  142.                     $pk_value = isset($row[$primary_key]) ? htmlspecialchars($row[$primary_key]) : '';
  143.                     $html .= '<tr data-pk-value="' . $pk_value . '">';
  144.                     foreach ($headers as $header) {
  145.                         $html .= '<td contenteditable="true" data-col-name="' . htmlspecialchars($header) . '">' . htmlspecialchars($row[$header]) . '</td>';
  146.                     }
  147.                     $html .= '</tr>';
  148.                 }
  149.                 $html .= '</tbody></table></div>';
  150.                 $html .= '<button id="updateChangesBtn" class="btn btn-primary mt-3 me-2">Save All Changes</button>';
  151.                 $result->free();
  152.             } catch (mysqli_sql_exception $e) {
  153.                 // Log the specific query error
  154.                 log_error("Query execution failed: '$query'. Error: " . $e->getMessage(), __FILE__, __LINE__);
  155.                 // OLD: $html = '<div class="alert alert-danger mt-3" role="alert">Query error: ' . $mysqli->error . '</div>';
  156.                 // NEW: A more polite and informative error message for the user
  157.                 $html = '<div class="alert alert-danger mt-3" role="alert">An error occurred while executing your query. Please double-check the syntax.</div>';
  158.             }
  159.  
  160.             echo $html;
  161.             exit;
  162.         }
  163.  
  164.         // Action 3: Update records in the database
  165.         if ($action === 'update_records') {
  166.             $table_name = isset($_POST['table']) ? clean_input($_POST['table']) : '';
  167.             $primary_key = isset($_POST['primary_key']) ? clean_input($_POST['primary_key']) : '';
  168.             $data = isset($_POST['data']) ? json_decode($_POST['data'], true) : [];
  169.            
  170.             if ($data === null) {
  171.                 log_error("Invalid JSON data received for update.", __FILE__, __LINE__);
  172.                 echo json_encode(['success' => false, 'message' => 'Invalid data received from the client.']);
  173.                 exit;
  174.             }
  175.  
  176.             if (empty($table_name) || empty($primary_key)) {
  177.                 log_error("Table name or primary key is missing for update.", __FILE__, __LINE__);
  178.                 echo json_encode(['success' => false, 'message' => 'Table name or primary key is missing.']);
  179.                 exit;
  180.             }
  181.  
  182.             if (empty($data)) {
  183.                 echo json_encode(['success' => false, 'message' => 'No changes were detected for the update.']);
  184.                 exit;
  185.             }
  186.  
  187.             $success_count = 0;
  188.             $error_messages = [];
  189.  
  190.             foreach ($data as $row) {
  191.                 try {
  192.                     if (isset($row['pk_value']) && isset($row['changes'])) {
  193.                         $pk_value = clean_input($row['pk_value']);
  194.                         $changes = $row['changes'];
  195.  
  196.                         $set_parts = [];
  197.                         foreach ($changes as $col => $val) {
  198.                             $sanitized_col = clean_input($col);
  199.                             $sanitized_val = clean_input($val);
  200.                             // Using prepared statements is the gold standard for security.
  201.                             // For this dynamic use case, we are still building the query string, but
  202.                             // we are sanitizing all inputs to minimize risk.
  203.                             $set_parts[] = "`" . $sanitized_col . "` = '" . $sanitized_val . "'";
  204.                         }
  205.  
  206.                         if (!empty($set_parts)) {
  207.                             $set_clause = implode(', ', $set_parts);
  208.                             $update_query = "UPDATE `$table_name` SET $set_clause WHERE `" . clean_input($primary_key) . "` = '$pk_value'";
  209.                            
  210.                             if ($mysqli->query($update_query) === TRUE) {
  211.                                 $success_count++;
  212.                             } else {
  213.                                 // This should be caught by the exception handler, but is kept as a failsafe
  214.                                 throw new mysqli_sql_exception("Update query returned false.");
  215.                             }
  216.                         }
  217.                     }
  218.                 } catch (mysqli_sql_exception $e) {
  219.                     // Log the update error and the specific record that failed
  220.                     log_error("Update failed for record with PK '$pk_value' on table '$table_name'. Error: " . $e->getMessage(), __FILE__, __LINE__);
  221.                     $error_messages[] = "An error occurred while updating the record with " . htmlspecialchars($primary_key) . " = " . htmlspecialchars($pk_value) . ".";
  222.                 }
  223.             }
  224.  
  225.             if (empty($error_messages)) {
  226.                 echo json_encode(['success' => true, 'message' => "Successfully updated " . htmlspecialchars($success_count) . " records."]);
  227.             } else {
  228.                 echo json_encode(['success' => false, 'message' => implode('<br>', $error_messages)]);
  229.             }
  230.             exit;
  231.         }
  232.  
  233.         // Action 4: Export data to a CSV file
  234.         if ($action === 'export_csv') {
  235.             $query = isset($_POST['query']) ? trim($_POST['query']) : '';
  236.             $table_name = isset($_POST['table']) ? clean_input($_POST['table']) : 'data';
  237.  
  238.             if (empty($query)) {
  239.                 die("The query is empty. Please enter a valid query to export data.");
  240.             }
  241.  
  242.             try {
  243.                 $result = $mysqli->query($query);
  244.                 if ($result === false) {
  245.                     throw new mysqli_sql_exception("Export query returned false.");
  246.                 }
  247.  
  248.                 // Set headers for CSV download
  249.                 $filename = $table_name . '_' . date('Y_m_d') . '.csv';
  250.                 header('Content-Type: text/csv; charset=utf-8');
  251.                 header('Content-Disposition: attachment; filename="' . $filename . '"');
  252.                
  253.                 $output = fopen('php://output', 'w');
  254.                
  255.                 // Get column headers and write to CSV
  256.                 $field_info = $result->fetch_fields();
  257.                 $headers = [];
  258.                 foreach ($field_info as $val) {
  259.                     $headers[] = $val->name;
  260.                 }
  261.                 fputcsv($output, $headers);
  262.  
  263.                 // Write data rows to CSV
  264.                 while ($row = $result->fetch_assoc()) {
  265.                     fputcsv($output, $row);
  266.                 }
  267.                
  268.                 fclose($output);
  269.                 $result->free();
  270.             } catch (mysqli_sql_exception $e) {
  271.                 log_error("CSV export failed for query '$query'. Error: " . $e->getMessage(), __FILE__, __LINE__);
  272.                 die("An error occurred during the CSV export process. Please check the logs.");
  273.             }
  274.             exit;
  275.         }
  276.  
  277.         // Action 5: New Action to get unique values for a column
  278.         // NEW: This action is added to support the dynamic filter dropdowns
  279.         if ($action === 'get_column_values') {
  280.             $table_name = isset($_POST['table']) ? clean_input($_POST['table']) : '';
  281.             $column_name = isset($_POST['column']) ? clean_input($_POST['column']) : '';
  282.            
  283.             if (empty($table_name) || empty($column_name)) {
  284.                 die(json_encode(['error' => 'Table name or column name is missing.']));
  285.             }
  286.  
  287.             $response = ['values' => []];
  288.  
  289.             try {
  290.                 // Fetch unique values from the column, sorted by value
  291.                 $query = "SELECT DISTINCT `" . $column_name . "` FROM `" . $table_name . "` ORDER BY `" . $column_name . "`";
  292.                 $result = $mysqli->query($query);
  293.                 if ($result) {
  294.                     while ($row = $result->fetch_row()) {
  295.                         $response['values'][] = $row[0];
  296.                     }
  297.                     $result->free();
  298.                 }
  299.  
  300.                 header('Content-Type: application/json');
  301.                 echo json_encode($response);
  302.             } catch (mysqli_sql_exception $e) {
  303.                 log_error("Failed to get unique values for column '$column_name' on table '$table_name': " . $e->getMessage(), __FILE__, __LINE__);
  304.                 die(json_encode(['error' => 'An error occurred while fetching column values.']));
  305.             }
  306.             exit;
  307.         }
  308.     }
  309. } catch (Exception $e) {
  310.     // Catch-all for any uncaught exceptions
  311.     log_error("An unexpected error occurred: " . $e->getMessage(), __FILE__, __LINE__);
  312.     // Provide a generic, polite message to the user
  313.     die("An unexpected error occurred on the server. We apologize for the inconvenience.");
  314. }
  315.  
  316. // --- Main PHP logic for initial page load ---
  317. // Get the list of all tables in the database
  318. $tables = [];
  319. try {
  320.     $result = $mysqli->query("SHOW TABLES FROM `$db_name`");
  321.     if ($result) {
  322.         while ($row = $result->fetch_row()) {
  323.             $tables[] = $row[0];
  324.         }
  325.         $result->free();
  326.         sort($tables);
  327.     }
  328. } catch (mysqli_sql_exception $e) {
  329.     log_error("Failed to list tables: " . $e->getMessage(), __FILE__, __LINE__);
  330.     // Continue with an empty array of tables, the frontend will display an error message
  331. }
  332.  
  333. // Close the connection
  334. $mysqli->close();
  335. ?>
  336.  
  337. <!DOCTYPE html>
  338. <html lang="en">
  339. <head>
  340.     <meta charset="UTF-8">
  341.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  342.     <title>Database Explorer & Editor</title>
  343.     <!-- Minimal Bootstrap CSS for styling -->
  344.     <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  345.     <style>
  346.         body {
  347.             background-color: #f8f9fa;
  348.         }
  349.         .container {
  350.             max-width: 1200px;
  351.         }
  352.         .card {
  353.             border: 1px solid #dee2e6;
  354.             box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
  355.         }
  356.         .table td[contenteditable="true"] {
  357.             cursor: pointer;
  358.             background-color: #fffaf0;
  359.         }
  360.         .table td:focus {
  361.             outline: 2px solid #0d6efd;
  362.             background-color: #fff;
  363.         }
  364.         /* Style for the floating Go to Top button */
  365.         #go-to-top-btn {
  366.             position: fixed;
  367.             bottom: 20px;
  368.             right: 20px;
  369.             display: none; /* Hidden by default */
  370.             z-index: 99;
  371.             border: none;
  372.             outline: none;
  373.             background-color: #0d6efd;
  374.             color: white;
  375.             cursor: pointer;
  376.             padding: 15px;
  377.             border-radius: 50%;
  378.             font-size: 18px;
  379.             box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  380.             transition: opacity 0.3s;
  381.         }
  382.  
  383.         #go-to-top-btn:hover {
  384.             opacity: 0.8;
  385.         }
  386.     </style>
  387. </head>
  388. <body>
  389.     <div class="container my-5">
  390.         <h1 class="text-center mb-4">Database Explorer & Editor</h1>
  391.        
  392.         <div class="card p-4">
  393.             <!-- Table Selection -->
  394.             <div class="mb-4">
  395.                 <label for="tableSelect" class="form-label fw-bold">1. Please select a table:</label>
  396.                 <select id="tableSelect" class="form-select">
  397.                     <option value="" disabled selected>Choose a table</option>
  398.                     <?php
  399.                     foreach ($tables as $table) {
  400.                         echo '<option value="' . htmlspecialchars($table) . '">' . htmlspecialchars($table) . '</option>';
  401.                     }
  402.                     ?>
  403.                 </select>
  404.             </div>
  405.  
  406.             <div id="tableInfo" class="mb-4">
  407.                 <!-- Primary Key and Auto-Increment Info will be displayed here -->
  408.             </div>
  409.  
  410.             <!-- Column Selection -->
  411.             <div id="columnCheckboxes" class="mb-4 d-none">
  412.                 <label class="form-label fw-bold">2. Please select the columns to display and filter:</label>
  413.                 <div class="mb-2">
  414.                     <button id="selectAllBtn" class="btn btn-sm btn-outline-secondary">Select All</button>
  415.                     <button id="selectNoneBtn" class="btn btn-sm btn-outline-secondary">Select None</button>
  416.                     <button id="toggleSelectionBtn" class="btn btn-sm btn-outline-secondary">Toggle Selection</button>
  417.                 </div>
  418.                 <!-- OLD: <div id="columnList" class="border p-3 rounded"> -->
  419.                 <!-- NEW: Updated structure to accommodate inline dropdowns -->
  420.                 <div id="columnList" class="border p-3 rounded d-flex flex-wrap gap-3">
  421.                     <!-- Checkboxes will be dynamically added here -->
  422.                 </div>
  423.             </div>
  424.  
  425.             <!-- Query Box -->
  426.             <div id="queryContainer" class="mb-4 d-none">
  427.                 <label for="queryBox" class="form-label fw-bold">3. Your Query:</label>
  428.                 <textarea id="queryBox" class="form-control" rows="3"></textarea>
  429.             </div>
  430.  
  431.             <!-- Action Buttons -->
  432.             <div id="buttonContainer" class="mb-4 d-none">
  433.                 <button id="executeQueryBtn" class="btn btn-primary me-2">Execute Query</button>
  434.                 <button id="exportCsvBtn" class="btn btn-success">Export to CSV</button>
  435.             </div>
  436.         </div>
  437.  
  438.         <!-- Query Result and Update Section -->
  439.         <div class="mt-4">
  440.             <h2 class="text-center mb-3">Query Results</h2>
  441.             <div id="resultContainer">
  442.                 <!-- Query results will be displayed here -->
  443.             </div>
  444.             <div id="messageBox" class="mt-3">
  445.                 <!-- Status messages (e.g., success/error) will be displayed here -->
  446.             </div>
  447.         </div>
  448.        
  449.         <!-- Floating Go to Top button -->
  450.         <button id="go-to-top-btn" title="Go to top">&#8679;</button>
  451.  
  452.         <!-- Hidden form for CSV export -->
  453.         <form id="exportForm" method="POST" action="" class="d-none">
  454.             <input type="hidden" name="action" value="export_csv">
  455.             <input type="hidden" name="table" id="exportTableName">
  456.             <input type="hidden" name="query" id="exportQuery">
  457.         </form>
  458.  
  459.     </div>
  460.  
  461.     <!-- Minimal Bootstrap JS -->
  462.     <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
  463.     <script>
  464.     // --- JavaScript Frontend Logic ---
  465.     document.addEventListener('DOMContentLoaded', function() {
  466.         const tableSelect = document.getElementById('tableSelect');
  467.         const tableInfo = document.getElementById('tableInfo');
  468.         const columnCheckboxes = document.getElementById('columnCheckboxes');
  469.         const columnList = document.getElementById('columnList');
  470.         const queryBox = document.getElementById('queryBox');
  471.         const queryContainer = document.getElementById('queryContainer');
  472.         const buttonContainer = document.getElementById('buttonContainer');
  473.         const executeQueryBtn = document.getElementById('executeQueryBtn');
  474.         const exportCsvBtn = document.getElementById('exportCsvBtn');
  475.         const resultContainer = document.getElementById('resultContainer');
  476.         const messageBox = document.getElementById('messageBox');
  477.         const exportForm = document.getElementById('exportForm');
  478.         const exportTableName = document.getElementById('exportTableName');
  479.         const exportQuery = document.getElementById('exportQuery');
  480.         const goToTopBtn = document.getElementById('go-to-top-btn');
  481.  
  482.         const selectAllBtn = document.getElementById('selectAllBtn');
  483.         const selectNoneBtn = document.getElementById('selectNoneBtn');
  484.         const toggleSelectionBtn = document.getElementById('toggleSelectionBtn');
  485.  
  486.         let selectedTable = '';
  487.         let primaryKey = '';
  488.         let autoIncrement = '';
  489.  
  490.         // Function to show a temporary message
  491.         function showMessage(msg, type) {
  492.             messageBox.innerHTML = `<div class="alert alert-${type}" role="alert">${msg}</div>`;
  493.             setTimeout(() => {
  494.                 messageBox.innerHTML = '';
  495.             }, 5000);
  496.         }
  497.  
  498.         // Event listener for table selection dropdown
  499.         tableSelect.addEventListener('change', function() {
  500.             selectedTable = this.value;
  501.             resultContainer.innerHTML = '';
  502.             messageBox.innerHTML = '';
  503.  
  504.             if (selectedTable) {
  505.                 const formData = new FormData();
  506.                 formData.append('action', 'get_columns');
  507.                 formData.append('table', selectedTable);
  508.  
  509.                 fetch('', {
  510.                     method: 'POST',
  511.                     body: formData
  512.                 })
  513.                 .then(response => response.json())
  514.                 .then(data => {
  515.                     if (data.error) {
  516.                         showMessage(data.error, 'danger');
  517.                         return;
  518.                     }
  519.  
  520.                     primaryKey = data.primary_key;
  521.                     autoIncrement = data.auto_increment;
  522.                     let infoHtml = `<p class="mb-1"><strong>Primary Key:</strong> ${primaryKey || 'None'}</p>`;
  523.                     if (autoIncrement) {
  524.                          infoHtml += `<p><strong>Auto-Increment Column:</strong> ${autoIncrement}</p>`;
  525.                     }
  526.                     tableInfo.innerHTML = infoHtml;
  527.                    
  528.                     columnCheckboxes.classList.remove('d-none');
  529.                     queryContainer.classList.remove('d-none');
  530.                     buttonContainer.classList.remove('d-none');
  531.                     columnList.innerHTML = '';
  532.                    
  533.                     data.columns.forEach(col => {
  534.                         // OLD: const div = document.createElement('div');
  535.                         // OLD: div.className = 'form-check form-check-inline';
  536.                         // OLD: div.innerHTML = `<input class="form-check-input" type="checkbox" id="col-${col}" value="${col}" checked> <label class="form-check-label" for="col-${col}">${col}</label>`;
  537.                         // OLD: columnList.appendChild(div);
  538.  
  539.                         // NEW: Create a container for the checkbox and dropdown
  540.                         const itemContainer = document.createElement('div');
  541.                         itemContainer.className = 'd-flex flex-column align-items-start';
  542.                         itemContainer.dataset.columnName = col;
  543.                        
  544.                         // Create checkbox and label
  545.                         const checkboxDiv = document.createElement('div');
  546.                         checkboxDiv.className = 'form-check';
  547.                         checkboxDiv.innerHTML = `
  548.                             <input class="form-check-input" type="checkbox" id="col-${col}" value="${col}" checked>
  549.                             <label class="form-check-label" for="col-${col}">${col}</label>
  550.                         `;
  551.                        
  552.                         // Append to the list and set up a change listener for the checkbox
  553.                         itemContainer.appendChild(checkboxDiv);
  554.                         columnList.appendChild(itemContainer);
  555.  
  556.                         // Fetch unique values when a column is checked
  557.                         if (checkboxDiv.querySelector('input').checked) {
  558.                             fetchColumnValues(col, itemContainer);
  559.                         }
  560.                     });
  561.  
  562.                     updateQueryBox();
  563.                 })
  564.                 .catch(error => {
  565.                     console.error('Error:', error);
  566.                     showMessage('An error occurred while fetching table information.', 'danger');
  567.                 });
  568.             } else {
  569.                 columnCheckboxes.classList.add('d-none');
  570.                 queryContainer.classList.add('d-none');
  571.                 buttonContainer.classList.add('d-none');
  572.                 tableInfo.innerHTML = '';
  573.             }
  574.         });
  575.  
  576.         // NEW: Function to fetch and create the multi-select dropdown
  577.         function fetchColumnValues(column, container) {
  578.             const formData = new FormData();
  579.             formData.append('action', 'get_column_values');
  580.             formData.append('table', selectedTable);
  581.             formData.append('column', column);
  582.  
  583.             fetch('', {
  584.                 method: 'POST',
  585.                 body: formData
  586.             })
  587.             .then(response => response.json())
  588.             .then(data => {
  589.                 if (data.error) {
  590.                     // OLD: No specific error handling for this feature.
  591.                     // NEW: Provide a polite message and log the error.
  592.                     console.error(data.error);
  593.                     const errorMsg = document.createElement('small');
  594.                     errorMsg.className = 'text-danger';
  595.                     errorMsg.textContent = 'Error fetching values.';
  596.                     container.appendChild(errorMsg);
  597.                     return;
  598.                 }
  599.                
  600.                 // Create the multi-select dropdown
  601.                 const selectElement = document.createElement('select');
  602.                 selectElement.id = `select-${column}`;
  603.                 selectElement.className = 'form-select form-select-sm mt-1';
  604.                 selectElement.multiple = true;
  605.                 selectElement.style.minWidth = '150px';
  606.                 selectElement.style.height = '80px';
  607.                
  608.                 data.values.forEach(val => {
  609.                     const option = document.createElement('option');
  610.                     option.value = val;
  611.                     option.textContent = val;
  612.                     selectElement.appendChild(option);
  613.                 });
  614.  
  615.                 container.appendChild(selectElement);
  616.                 // Listen for changes on the new dropdown to update the query box
  617.                 selectElement.addEventListener('change', updateQueryBox);
  618.             })
  619.             .catch(error => {
  620.                 console.error('Error:', error);
  621.                 showMessage(`An error occurred while fetching values for column '${column}'.`, 'danger');
  622.             });
  623.         }
  624.        
  625.         // Event listener for column checkboxes to update the query box
  626.         columnList.addEventListener('change', function(e) {
  627.             if (e.target.matches('input[type="checkbox"]')) {
  628.                 const column = e.target.value;
  629.                 const container = e.target.closest('[data-column-name]');
  630.                 const existingSelect = container.querySelector('select');
  631.                
  632.                 if (e.target.checked) {
  633.                     // Checkbox is checked, but we might have a pre-existing dropdown from a previous uncheck.
  634.                     if (!existingSelect) {
  635.                         fetchColumnValues(column, container);
  636.                     } else {
  637.                         existingSelect.style.display = '';
  638.                     }
  639.                 } else {
  640.                     // Checkbox is unchecked, hide the dropdown.
  641.                     if (existingSelect) {
  642.                         existingSelect.style.display = 'none';
  643.                     }
  644.                 }
  645.                 updateQueryBox();
  646.             }
  647.         });
  648.  
  649.         selectAllBtn.addEventListener('click', function() {
  650.             const checkboxes = columnList.querySelectorAll('input[type="checkbox"]');
  651.             checkboxes.forEach(checkbox => {
  652.                 checkbox.checked = true;
  653.                 const container = checkbox.closest('[data-column-name]');
  654.                 const existingSelect = container.querySelector('select');
  655.                 if (existingSelect) {
  656.                     existingSelect.style.display = '';
  657.                 } else {
  658.                     fetchColumnValues(checkbox.value, container);
  659.                 }
  660.             });
  661.             updateQueryBox();
  662.         });
  663.  
  664.         selectNoneBtn.addEventListener('click', function() {
  665.             const checkboxes = columnList.querySelectorAll('input[type="checkbox"]');
  666.             checkboxes.forEach(checkbox => {
  667.                 checkbox.checked = false;
  668.                 const existingSelect = checkbox.closest('[data-column-name]').querySelector('select');
  669.                 if (existingSelect) {
  670.                     existingSelect.style.display = 'none';
  671.                 }
  672.             });
  673.             updateQueryBox();
  674.         });
  675.  
  676.         toggleSelectionBtn.addEventListener('click', function() {
  677.             const checkboxes = columnList.querySelectorAll('input[type="checkbox"]');
  678.             checkboxes.forEach(checkbox => {
  679.                 checkbox.checked = !checkbox.checked;
  680.                 const container = checkbox.closest('[data-column-name]');
  681.                 const existingSelect = container.querySelector('select');
  682.                 if (checkbox.checked) {
  683.                     if (existingSelect) {
  684.                         existingSelect.style.display = '';
  685.                     } else {
  686.                         fetchColumnValues(checkbox.value, container);
  687.                     }
  688.                 } else {
  689.                     if (existingSelect) {
  690.                         existingSelect.style.display = 'none';
  691.                     }
  692.                 }
  693.             });
  694.             updateQueryBox();
  695.         });
  696.        
  697.         // NEW: Refactored function to build the query from selected columns and filter values
  698.         function updateQueryBox() {
  699.             const checkedCols = Array.from(columnList.querySelectorAll('input:checked'))
  700.                 .map(input => `\`${input.value}\``);
  701.            
  702.             const whereClauses = [];
  703.             const allItemContainers = columnList.querySelectorAll('[data-column-name]');
  704.            
  705.             allItemContainers.forEach(container => {
  706.                 const column = container.dataset.columnName;
  707.                 const checkbox = container.querySelector('input[type="checkbox"]');
  708.                 const select = container.querySelector('select');
  709.                
  710.                 if (checkbox && checkbox.checked && select && select.style.display !== 'none') {
  711.                     const selectedOptions = Array.from(select.selectedOptions).map(option => option.value);
  712.                     if (selectedOptions.length > 0) {
  713.                         const formattedValues = selectedOptions.map(val => `'${val.replace(/'/g, "\\'")}'`).join(', ');
  714.                         whereClauses.push(`\`${column}\` IN (${formattedValues})`);
  715.                     }
  716.                 }
  717.             });
  718.            
  719.             const selectClause = checkedCols.length > 0 ? checkedCols.join(', ') : '*';
  720.             const whereClause = whereClauses.length > 0 ? ' WHERE ' + whereClauses.join(' AND ') : '';
  721.            
  722.             queryBox.value = `SELECT ${selectClause} FROM \`${selectedTable}\`${whereClause} LIMIT 10`;
  723.         }
  724.  
  725.         // Event listener for "Execute Query" button
  726.         executeQueryBtn.addEventListener('click', function() {
  727.             const query = queryBox.value;
  728.             if (!query) {
  729.                 showMessage('The query box cannot be empty. Please enter a valid query.', 'warning');
  730.                 return;
  731.             }
  732.  
  733.             const formData = new FormData();
  734.             formData.append('action', 'execute_query');
  735.             formData.append('table', selectedTable);
  736.             formData.append('query', query);
  737.             formData.append('primary_key', primaryKey);
  738.            
  739.             executeQueryBtn.disabled = true;
  740.             executeQueryBtn.textContent = 'Executing...';
  741.  
  742.             fetch('', {
  743.                 method: 'POST',
  744.                 body: formData
  745.             })
  746.             .then(response => response.text())
  747.             .then(html => {
  748.                 resultContainer.innerHTML = html;
  749.                 executeQueryBtn.disabled = false;
  750.                 executeQueryBtn.textContent = 'Execute Query';
  751.                
  752.                 const updateChangesBtn = document.getElementById('updateChangesBtn');
  753.                 if (updateChangesBtn) {
  754.                     updateChangesBtn.addEventListener('click', updateRecords);
  755.                 }
  756.                
  757.                 const cells = resultContainer.querySelectorAll('td[contenteditable="true"]');
  758.                 cells.forEach(cell => {
  759.                     cell.dataset.originalValue = cell.textContent.trim();
  760.                 });
  761.             })
  762.             .catch(error => {
  763.                 console.error('Error:', error);
  764.                 showMessage('An unexpected error occurred while executing the query. Please check the console for more details.', 'danger');
  765.                 executeQueryBtn.disabled = false;
  766.                 executeQueryBtn.textContent = 'Execute Query';
  767.             });
  768.         });
  769.  
  770.         // Event listener for "Export to CSV" button
  771.         exportCsvBtn.addEventListener('click', function() {
  772.             const query = queryBox.value;
  773.             if (!query) {
  774.                 showMessage('The query box cannot be empty. Please enter a valid query to export.', 'warning');
  775.                 return;
  776.             }
  777.            
  778.             exportTableName.value = selectedTable;
  779.             exportQuery.value = query;
  780.             exportForm.submit();
  781.         });
  782.  
  783.         // Function to handle updating multiple records
  784.         function updateRecords() {
  785.             const tableRows = resultContainer.querySelectorAll('tbody tr');
  786.             const updates = [];
  787.            
  788.             if (!primaryKey) {
  789.                 showMessage('Cannot update. A primary key was not found for this table.', 'danger');
  790.                 return;
  791.             }
  792.  
  793.             tableRows.forEach(row => {
  794.                 const pkValue = row.dataset.pkValue;
  795.                 if (!pkValue) return;
  796.  
  797.                 const changes = {};
  798.                 const cells = row.querySelectorAll('td[contenteditable="true"]');
  799.                 cells.forEach(cell => {
  800.                     const colName = cell.dataset.colName;
  801.                     const currentValue = cell.textContent.trim();
  802.                     const originalValue = cell.dataset.originalValue;
  803.                    
  804.                     if (currentValue !== originalValue) {
  805.                         changes[colName] = currentValue;
  806.                     }
  807.                 });
  808.                
  809.                 if (Object.keys(changes).length > 0) {
  810.                     updates.push({
  811.                         pk_value: pkValue,
  812.                         changes: changes
  813.                     });
  814.                 }
  815.             });
  816.            
  817.             if (updates.length === 0) {
  818.                 showMessage('No changes were detected in the table. Nothing to save.', 'info');
  819.                 return;
  820.             }
  821.            
  822.             const formData = new FormData();
  823.             formData.append('action', 'update_records');
  824.             formData.append('table', selectedTable);
  825.             formData.append('primary_key', primaryKey);
  826.             formData.append('data', JSON.stringify(updates));
  827.            
  828.             const updateBtn = document.getElementById('updateChangesBtn');
  829.             updateBtn.disabled = true;
  830.             updateBtn.textContent = 'Updating...';
  831.            
  832.             fetch('', {
  833.                 method: 'POST',
  834.                 body: formData
  835.             })
  836.             .then(response => response.json())
  837.             .then(data => {
  838.                 if (data.success) {
  839.                     showMessage(data.message, 'success');
  840.                     const cells = resultContainer.querySelectorAll('td[contenteditable="true"]');
  841.                     cells.forEach(cell => {
  842.                         cell.dataset.originalValue = cell.textContent.trim();
  843.                     });
  844.                    
  845.                 } else {
  846.                     showMessage(data.message, 'danger');
  847.                 }
  848.                 updateBtn.disabled = false;
  849.                 updateBtn.textContent = 'Save All Changes';
  850.             })
  851.             .catch(error => {
  852.                 console.error('Error:', error);
  853.                 showMessage('An unexpected error occurred during the update process. Please check the console for more details.', 'danger');
  854.                 updateBtn.disabled = false;
  855.                 updateBtn.textContent = 'Save All Changes';
  856.             });
  857.         }
  858.        
  859.         // --- Go to Top button functionality ---
  860.         window.onscroll = function() {
  861.             if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
  862.                 goToTopBtn.style.display = "block";
  863.             } else {
  864.                 goToTopBtn.style.display = "none";
  865.             }
  866.         };
  867.  
  868.         goToTopBtn.addEventListener('click', function() {
  869.             document.body.scrollTop = 0; // For Safari
  870.             document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
  871.         });
  872.     });
  873.     </script>
  874. </body>
  875. </html>
  876.  
Advertisement
Add Comment
Please, Sign In to add comment