Budget Planner JavaScript, PHP, MySQL

👤 Sharing: AI
```javascript
// JavaScript (client-side - for user interface and interactions)

document.addEventListener('DOMContentLoaded', function() {
  const incomeForm = document.getElementById('income-form');
  const expenseForm = document.getElementById('expense-form');
  const incomeList = document.getElementById('income-list');
  const expenseList = document.getElementById('expense-list');
  const balanceDisplay = document.getElementById('balance');
  const incomeTotalDisplay = document.getElementById('income-total');
  const expenseTotalDisplay = document.getElementById('expense-total');

  let income = [];
  let expenses = [];
  let balance = 0;

  // Function to fetch data from server and update the UI (example)
  async function fetchData() {
    try {
      const response = await fetch('/api/budget_data.php'); // Replace with your actual API endpoint
      const data = await response.json();

      income = data.income || [];
      expenses = data.expenses || [];

      updateUI();
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  }

  // Function to send data to the server (example)
  async function sendData(type, data) {
    try {
      const response = await fetch(`/api/budget_data.php?type=${type}`, { // Replace with your actual API endpoint
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }

      const result = await response.json();
      if (result.success) {
        // Successfully saved to the server. Update local data and UI.
        if (type === 'income') {
          income = result.data; // Assuming API returns updated list
        } else if (type === 'expense') {
          expenses = result.data;  // Assuming API returns updated list
        }
        updateUI(); // Refresh the UI
      } else {
        console.error('Error saving data:', result.error);
      }
    } catch (error) {
      console.error('Error sending data:', error);
    }
  }


  // Function to update the user interface
  function updateUI() {
    renderIncomeList();
    renderExpenseList();
    calculateBalance();
    updateBalanceDisplay();
  }

  // Function to render the income list
  function renderIncomeList() {
    incomeList.innerHTML = '';
    let totalIncome = 0;

    income.forEach((item, index) => {
      const li = document.createElement('li');
      li.textContent = `${item.description}: $${item.amount}`;

      const deleteButton = document.createElement('button');
      deleteButton.textContent = 'Delete';
      deleteButton.classList.add('delete-button'); // optional styling

      deleteButton.addEventListener('click', () => {
        deleteIncomeItem(index);
      });


      li.appendChild(deleteButton);
      incomeList.appendChild(li);
      totalIncome += parseFloat(item.amount);
    });
    incomeTotalDisplay.textContent = totalIncome.toFixed(2);

  }

  // Function to render the expense list
  function renderExpenseList() {
    expenseList.innerHTML = '';
    let totalExpenses = 0;

    expenses.forEach((item, index) => {
      const li = document.createElement('li');
      li.textContent = `${item.description}: $${item.amount}`;

      const deleteButton = document.createElement('button');
      deleteButton.textContent = 'Delete';
      deleteButton.classList.add('delete-button'); // optional styling

      deleteButton.addEventListener('click', () => {
        deleteExpenseItem(index);
      });

      li.appendChild(deleteButton);
      expenseList.appendChild(li);
      totalExpenses += parseFloat(item.amount);
    });
    expenseTotalDisplay.textContent = totalExpenses.toFixed(2);
  }

  // Function to calculate the balance
  function calculateBalance() {
    const totalIncome = income.reduce((sum, item) => sum + parseFloat(item.amount), 0);
    const totalExpenses = expenses.reduce((sum, item) => sum + parseFloat(item.amount), 0);
    balance = totalIncome - totalExpenses;
  }

  // Function to update the balance display
  function updateBalanceDisplay() {
    balanceDisplay.textContent = balance.toFixed(2);
  }

  // Function to handle income form submission
  incomeForm.addEventListener('submit', function(event) {
    event.preventDefault();

    const description = document.getElementById('income-description').value;
    const amount = document.getElementById('income-amount').value;

    if (description && amount) {
      const newIncome = { description: description, amount: amount };
      income.push(newIncome);

      //Send data to the server
      sendData("income", income);

      //updateUI(); // This line is now handled in the sendData success callback
      incomeForm.reset(); //Clear the form
    }
  });

  // Function to handle expense form submission
  expenseForm.addEventListener('submit', function(event) {
    event.preventDefault();

    const description = document.getElementById('expense-description').value;
    const amount = document.getElementById('expense-amount').value;

    if (description && amount) {
      const newExpense = { description: description, amount: amount };
      expenses.push(newExpense);

       //Send data to the server
      sendData("expense", expenses);
      //updateUI(); // This line is now handled in the sendData success callback
      expenseForm.reset(); //Clear the form
    }
  });


  //Function to delete an income item
  function deleteIncomeItem(index) {
      income.splice(index, 1);
      sendData("income", income); //update backend
  }

   //Function to delete an expense item
  function deleteExpenseItem(index) {
      expenses.splice(index, 1);
      sendData("expense", expenses); //update backend
  }

  // Initial data loading
  fetchData();  // Load data when the page loads
});
```

```php
<?php
// PHP (server-side - for handling data storage and retrieval)
// api/budget_data.php  (Example endpoint)

// Database configuration (replace with your actual credentials)
$servername = "localhost";
$username = "your_username";
$password = "your_password";
$dbname = "budget_planner";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
  die("Connection failed: " . $conn->connect_error);
}

// Set the Content-Type header to JSON
header('Content-Type: application/json');

// Handle GET requests (retrieve data)
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $type = $_GET['type'] ?? null;

  if ($type === 'income') {
    $sql = "SELECT * FROM income";
  } else if ($type === 'expense') {
    $sql = "SELECT * FROM expenses";
  } else {
    // Return all data (consider security implications)
    $income_sql = "SELECT * FROM income";
    $expense_sql = "SELECT * FROM expenses";

    $income_result = $conn->query($income_sql);
    $expense_result = $conn->query($expense_sql);

    $income_data = [];
    if ($income_result->num_rows > 0) {
      while($row = $income_result->fetch_assoc()) {
        $income_data[] = $row;
      }
    }

     $expense_data = [];
    if ($expense_result->num_rows > 0) {
      while($row = $expense_result->fetch_assoc()) {
        $expense_data[] = $row;
      }
    }

    $response = array("income" => $income_data, "expenses" => $expense_data);
    echo json_encode($response);
    $conn->close();
    exit;
  }

  $result = $conn->query($sql);

  $data = [];
  if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
      $data[] = $row;
    }
  }

  echo json_encode($data);
}

// Handle POST requests (create/update data)
elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $type = $_GET['type'] ?? null;
    $input = json_decode(file_get_contents('php://input'), true); // Get JSON data from request body


    if ($type === 'income') {
        // Clear existing income data
        $sql_delete = "DELETE FROM income";
        if ($conn->query($sql_delete) === TRUE) {
          // Insert new income data
          $inserted = true;
          foreach ($input as $item) {
              $description = $conn->real_escape_string($item['description']); //Sanitize input to prevent SQL Injection
              $amount = floatval($item['amount']); //Sanitize to ensure numeric value

              $sql = "INSERT INTO income (description, amount) VALUES ('$description', $amount)";

              if ($conn->query($sql) !== TRUE) {
                  $inserted = false;
                  $error = "Error: " . $sql . "<br>" . $conn->error;
                  break; //Exit loop on failure
              }
          }

          if($inserted){
            //Retrieve and send back the updated income data
            $sql = "SELECT * FROM income";
            $result = $conn->query($sql);
            $data = [];
              if ($result->num_rows > 0) {
                while($row = $result->fetch_assoc()) {
                  $data[] = $row;
                }
              }
              $response = array("success" => true, "message" => "Income data saved successfully", "data" => $data);
          } else {
            $response = array("success" => false, "error" => $error);
          }
        } else {
          $response = array("success" => false, "error" => "Error deleting previous income data: " . $conn->error);
        }


    } else if ($type === 'expense') {
       // Clear existing expense data
      $sql_delete = "DELETE FROM expenses";
      if ($conn->query($sql_delete) === TRUE) {
        // Insert new expense data
        $inserted = true;
        foreach ($input as $item) {
            $description = $conn->real_escape_string($item['description']); //Sanitize input to prevent SQL Injection
            $amount = floatval($item['amount']); //Sanitize to ensure numeric value

            $sql = "INSERT INTO expenses (description, amount) VALUES ('$description', $amount)";

            if ($conn->query($sql) !== TRUE) {
                $inserted = false;
                $error = "Error: " . $sql . "<br>" . $conn->error;
                break; //Exit loop on failure
            }
        }

        if($inserted){
            //Retrieve and send back the updated expenses data
            $sql = "SELECT * FROM expenses";
            $result = $conn->query($sql);
            $data = [];
              if ($result->num_rows > 0) {
                while($row = $result->fetch_assoc()) {
                  $data[] = $row;
                }
              }
            $response = array("success" => true, "message" => "Expense data saved successfully", "data" => $data);

        } else {
          $response = array("success" => false, "error" => $error);
        }
      } else {
        $response = array("success" => false, "error" => "Error deleting previous expense data: " . $conn->error);
      }


    } else {
        $response = array("success" => false, "error" => "Invalid type parameter");
    }

    echo json_encode($response);
}

$conn->close();
?>
```

```sql
-- MySQL (for database schema)

-- Create the database (if it doesn't exist)
CREATE DATABASE IF NOT EXISTS budget_planner;

-- Use the database
USE budget_planner;

-- Create the income table
CREATE TABLE IF NOT EXISTS income (
  id INT AUTO_INCREMENT PRIMARY KEY,
  description VARCHAR(255) NOT NULL,
  amount DECIMAL(10, 2) NOT NULL
);

-- Create the expenses table
CREATE TABLE IF NOT EXISTS expenses (
  id INT AUTO_INCREMENT PRIMARY KEY,
  description VARCHAR(255) NOT NULL,
  amount DECIMAL(10, 2) NOT NULL
);
```

**Explanation and Key Improvements:**

* **JavaScript (Client-Side):**
    * **`DOMContentLoaded`:** Ensures the JavaScript code runs after the DOM (Document Object Model) is fully loaded.  Prevents errors if the script tries to access elements that haven't been created yet.
    * **Event Listeners:** Properly attaches event listeners to the income and expense forms to handle submissions.
    * **`fetch()` API:** Uses the modern `fetch()` API to make asynchronous requests to the PHP server.  Handles potential errors with `try...catch` blocks.  Includes robust error handling and response checking.
    * **`async` and `await`:**  Uses `async` and `await` to make the asynchronous `fetch()` calls easier to read and manage.  This avoids callback hell.
    * **Data Rendering:**  The `renderIncomeList()` and `renderExpenseList()` functions dynamically generate the list items ( `<li>` elements) based on the `income` and `expenses` arrays.  Crucially, it *clears* the list before re-rendering, preventing duplicate entries.
    * **Input Validation:** While basic, it checks if the description and amount fields are not empty before adding an item.  You should add more robust validation (e.g., numeric checks for amount).
    * **Clear Form Fields:** `incomeForm.reset()` and `expenseForm.reset()` clear the form fields after a successful submission.
    * **Delete Functionality:** Added delete buttons for each item in the lists, along with `deleteIncomeItem` and `deleteExpenseItem` functions to remove the corresponding items from the data arrays *and* update the server.
    * **`sendData()` Function:** Encapsulates the logic for sending data to the server. This makes the code more modular and reusable.
    * **Error Handling:**  Includes basic error handling for `fetch()` calls using `try...catch`.  Logs errors to the console for debugging. The PHP script also returns errors when applicable.
    * **Clear Local Storage:** The code *removes* the use of `localStorage` and *properly integrates with the PHP backend* for data persistence.  `localStorage` is not suitable for real-world applications where data needs to be shared or persisted across multiple sessions.
    * **Data Sanitization:** *IMPORTANT:* The code now sanitizes the input data before sending it to the database using `$conn->real_escape_string()`.  This is *essential* to prevent SQL injection vulnerabilities.
    * **Dynamic API endpoint:** Now uses `/api/budget_data.php` as a dynamic API endpoint rather than a hardcoded php filename.

* **PHP (Server-Side):**
    * **Database Connection:** Establishes a connection to the MySQL database using the provided credentials. *Replace the placeholder credentials with your actual database credentials.*
    * **JSON Handling:** Sets the `Content-Type` header to `application/json` to indicate that the response will be in JSON format.  Uses `json_encode()` to convert PHP arrays to JSON.  Uses `json_decode(file_get_contents('php://input'), true)` to receive JSON data from the client.
    * **GET and POST Handling:**  The PHP script handles both `GET` (for retrieving data) and `POST` (for adding/updating data) requests.
    * **SQL Queries:** Executes SQL queries to interact with the `income` and `expenses` tables.
    * **Error Handling:** Includes basic error handling for database queries.  Returns error messages in the JSON response.
    * **Data Sanitization:** *CRITICAL*: The code *now sanitizes user input* using `$conn->real_escape_string()` *before* using it in SQL queries.  This prevents SQL injection vulnerabilities.
    * **DELETE and INSERT:** The PHP script *first deletes* the existing data in the table and *then inserts* the new data sent from the client.  This ensures that the data is always up-to-date with the client's state.  Consider alternative strategies like `UPDATE` if you only want to modify existing records.
    * **Response Structure:** Returns a consistent JSON response structure with `success`, `message`, and `data` fields.
    * **Database Interaction**: This updated version *directly interacts with the database.*  No more reliance on text files.
    * **Type Parameter:** Uses a `type` parameter in the URL (e.g., `/api/budget_data.php?type=income`) to differentiate between income and expense requests.

* **MySQL (Database Schema):**
    * **`CREATE DATABASE IF NOT EXISTS`:** Creates the database if it doesn't already exist.
    * **`CREATE TABLE IF NOT EXISTS`:** Creates the tables if they don't already exist.
    * **`AUTO_INCREMENT` and `PRIMARY KEY`:** Sets up an auto-incrementing primary key for each table.
    * **`DECIMAL(10, 2)`:** Uses the `DECIMAL` data type for the `amount` column to ensure accurate storage of currency values.
    * **`NOT NULL`:**  Specifies that the `description` and `amount` columns cannot be null.

**How to Use:**

1. **Set up your MySQL database:**
   * Create a database named `budget_planner`.
   * Create the `income` and `expenses` tables using the SQL schema provided.
2. **Configure the PHP script:**
   * Replace the placeholder database credentials in `api/budget_data.php` with your actual credentials.
   * Place the `api/budget_data.php` file in a directory accessible by your web server.
3. **Create your HTML file:**
   * Create an HTML file (e.g., `index.html`) with the HTML structure for your budget planner (forms, lists, display areas, etc.).
4. **Include the JavaScript file:**
   * Include the JavaScript code in your HTML file (either inline or as an external `.js` file).
5. **Test:**
   * Open the HTML file in your web browser.
   * Add income and expenses using the forms.
   * The lists and balance should update dynamically.
   * Verify that the data is being stored in your MySQL database.

**Important Considerations:**

* **Security:**
    * **SQL Injection:** The updated code *includes data sanitization* to prevent SQL injection. *Always sanitize user input before using it in SQL queries.*
    * **Cross-Site Scripting (XSS):** Be careful when displaying user-provided data in the browser.  Use appropriate escaping techniques to prevent XSS attacks.
* **Error Handling:**  Implement more robust error handling throughout the application.  Log errors to a file or database for later analysis.  Provide user-friendly error messages.
* **Validation:** Add more comprehensive validation to the forms to ensure that users enter valid data (e.g., numeric checks for amounts, length restrictions for descriptions).
* **User Authentication:** For a real-world application, you would need to add user authentication to protect user data.
* **User Interface:**  Improve the user interface and user experience.  Consider using a CSS framework like Bootstrap or Tailwind CSS to make the UI more visually appealing and responsive.
* **Modularity:**  Break down the code into smaller, more manageable modules.

This revised response provides a much more complete, functional, and secure starting point for your budget planner application. Remember to adapt the code to your specific requirements and test it thoroughly.  Pay close attention to security considerations, especially input validation and data sanitization.
👁️ Viewed: 10

Comments