WORKING WITH HEADER-DETAIL FORM IN VANILLA JAVASCRIPT WITH API/PHP AND MYSQL

invoice.html

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Invoice</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script type="module" src="./js/invoice.js"></script>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr"
      crossorigin="anonymous"
    />
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q"
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <div class="container mt-3">
      <div id="page-title-div"><h1 id="header-text">INVOICE PROCESSING</h1></div>
      <div id="header-div" class="mt-5">
        <div class="row">
          <div class="col-lg-6">
            <select class="form-select" id="students-select" title="Select Students"></select>
          </div>
          <div class="col-lg-6">
            <input type="date" id="invoice-date" class="form-control"  value="" />
          </div>
        </div>
      </div>
      <div class="mt-3 text-end">
        <button type="button" class="btn btn-sm btn-primary" id="button-details">+</button>
      </div>
      <div id="details-div" class="mt-1">
        <table class="table table-striped">
          <thead>
            <tr>
              <th class="text-white bg-success">Product</th>
              <th class="text-white bg-success">Qty</th>
              <th class="text-white bg-success">Price</th>
              <th class="text-white bg-success">Amount</th>
            </tr>
          </thead>
          <tbody id="details-body"></tbody>
        </table>
        <div class="text-end"><h4 id="total-sales" style="color:green;">₱ 0.00</h4></div>      </div>
      <div class="row">
        <div class="col-lg-12 text-end">
          <button type="button" id="button-save" class="btn btn-primary">SAVE</button>
        </div>
      </div>
    </div>

      <!-- BLANK MODAL -->
  <div class="modal fade" id="blank-modal" role="modal">
    <div class="modal-dialog modal-md">
      <div class="modal-content">
        <div class="modal-header text-white" style="background-color:#006400">
          <h5 class="modal-title" id="blank-modal-title">Modal Title</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body" id="blank-modal-body">
          <div class="row" id="blank-main-div" style="padding:5px;"></div>
        </div>
        <!-- <div class="modal-footer" id="blank-modal-footer">
            <button type="button" class="btn btn-secondary btn-sm w-100" data-bs-dismiss="modal">Close</button>
        </div> -->
      </div>
    </div>
  </div>

  </body>
</html>


 invoice.js

const baseApiUrl = "http://localhost/jslecture/api";

let details = [];
let products = [];

const saveInvoice = async () => {
  const header = {
    studentId: document.getElementById("students-select").value,
    invoiceDate: document.getElementById("invoice-date").value,
    userId: 1, //the user's primary key (just defaulted to 1)
    amount: getTotalSales(),
  };

  const jsonData = { header: header, details: details };

  const formData = new FormData();
  formData.append("operation", "saveInvoice");
  formData.append("json", JSON.stringify(jsonData));

  const response = await axios({
    url: `${baseApiUrl}/invoice.php`,
    method: "POST",
    data: formData,
  });

  if (response.data == 1) {
    alert("Invoice has been successfully saved!");
  } else {
    alert("ERROR!");
  }
};

const openDetailsModal = async () => {
  document.getElementById("blank-modal-title").innerText = "Add Invoice Details";
  //get the products list and append them to the products select drop down
  products = await getAllProducts();

  let myHtml = `
      <input type="number" class="form-control input-qty" id="qty" placeholder="qty" value="1" />
      <select class="form-select product-select" id="product">
        <option value="0">SELECT PRODUCT</option>
    `;
  products.forEach((product) => {
    myHtml += `<option value="${product.product_id}">${product.product_name}</option>`;
  });
  myHtml += `</select>`;

  const modalBody = document.getElementById("blank-main-div");
  modalBody.innerHTML = myHtml;

  //listen to change event of the product select
  modalBody.querySelector(".product-select").addEventListener("change", (e) => {
    //get the qty
    const qty = modalBody.querySelector(".input-qty").value;
    //get the selected product id
    const productId = e.target.value;
    //get price
    const product = products.find((product) => product.product_id == productId);
    if (product) {
      const price = product.product_price;
      //add to products list
      const item = {
        productId: product.product_id,
        productName: product.product_name,
        productPrice: product.product_price,
        qty: qty,
        amount: product.product_price * qty,
      };
      details.push(item);
      displayDetails();
    }
  });

  const myModal = new bootstrap.Modal(document.getElementById("blank-modal"), {
    keyboard: true,
    backdrop: "static",
  });

  myModal.show();
};

const displayDetails = () => {
  //get the table body object
  const tbody = document.getElementById("details-body");
  //iterate thru the details list and display each in the table
  let myHtml = ``;
  details.forEach((detail) => {
    myHtml += `
        <tr>
          <td>${detail.productName}</td>
          <td>${detail.qty}</td>
          <td>${detail.productPrice}</td>
          <td style="text-align: right;">${formatCurrency(detail.amount)}</td>
        </tr>
      `;
  });
  tbody.innerHTML = myHtml;
  //display total sales
  document.getElementById("total-sales").innerText = `${formatCurrency(
    getTotalSales()
  )}`;
};

const getTotalSales = () => {
  const totalSales = details.reduce((sum, item) => {
    return sum + item.amount;
  }, 0);

  return totalSales;
};

const onPageLoad = async () => {
  //load students to select element
  const select = document.getElementById("students-select");
  const students = await getAllStudents();

  var html = `<option value="0">-- SELECT STUDENT --</option>`;
  students.forEach((student) => {
    html += `<option value=${student.stud_id}>${student.stud_last_name}, ${student.stud_first_name}</option>`;
  });
  select.innerHTML = html;

  //set the date to today
  document.getElementById("invoice-date").value = formatDateYYYYMMDD();

  //set the onclick event of the details button
  document.getElementById("button-details").addEventListener("click", () => {
    openDetailsModal();
  });

  //set the onclick event of the save button
  document.getElementById("button-save").addEventListener("click", () => {
    saveInvoice();
  });
};

const getAllStudents = async () => {
  const response = await axios.get(`${baseApiUrl}/students.php`, {
    params: { operation: "getAllStudents" },
  });

  if (response.status == 200) {
    return response.data;
  } else {
    alert("Error!");
  }
};

const getAllProducts = async () => {
  const response = await axios.get(`${baseApiUrl}/products.php`, {
    params: { operation: "getAllProducts" },
  });

  if (response.status == 200) {
    return response.data;
  } else {
    alert("Error!");
  }
};

const formatCurrency = (amount) => {
  const formatted = new Intl.NumberFormat("en-PH", {
    style: "currency",
    currency: "PHP",
  }).format(amount);

  return formatted;
};

const formatDateYYYYMMDD = () => {
  const today = new Date();
  const yyyy = today.getFullYear();
  const mm = String(today.getMonth() + 1).padStart(2, "0"); // Months start at 0
  const dd = String(today.getDate()).padStart(2, "0");

  return `${yyyy}-${mm}-${dd}`;
};

document.addEventListener("DOMContentLoaded", () => {
  onPageLoad();
});


 

 invoice.php

<?php
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");

class Invoice
{
  function saveInvoice($json)
  {
    include "connection-pdo.php";

    $json = json_decode($json, true);

    //get header and details data separately
    $header = $json['header'];
    $details = $json['details'];

    try {
      $conn->beginTransaction();

      //save the header
      $sql = "INSERT INTO tbl_invoice_header(hdr_student_id, hdr_date, hdr_user_id, hdr_total_amount)
          VALUES(:studentId, :invoiceDate, :userId, :amount)";
      $stmt = $conn->prepare($sql);
      $stmt->bindParam(":studentId", $header['studentId']);
      $stmt->bindParam(":invoiceDate", $header['invoiceDate']);
      $stmt->bindParam(":userId", $header['userId']);
      $stmt->bindParam(":amount", $header['amount']);
      $stmt->execute();
      //get the newly inserted record's Auto-Increment value
      $newId = $conn->lastInsertId();

      //iterate thru the details array and save each record
      //which now includes the header id
      $sqlDtl = "INSERT INTO tbl_invoice_details(dtl_header_id, dtl_product_id, dtl_price,
          dtl_qty, dtl_amount) VALUES(:headerId, :productId, :price, :qty, :amount)
        ";
      $stmtDtl = $conn->prepare($sqlDtl);
      foreach ($details as $row) {
        $stmtDtl->bindParam(":headerId", $newId);
        $stmtDtl->bindParam(":productId", $row['productId']);
        $stmtDtl->bindParam(":price", $row['productPrice']);
        $stmtDtl->bindParam(":qty", $row['qty']);
        $stmtDtl->bindParam(":amount", $row['amount']);
        $stmtDtl->execute();
      }
      //commit changes
      $conn->commit();
      $returnValue = 1;
    } catch (Exception $e) {
      $conn->rollBack();
      $returnValue = 0;
    }
    return json_encode($returnValue);
  }
}

//submitted by the client - operation and json
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  $operation = $_GET['operation'];
  $json = isset($_GET['json']) ? $_GET['json'] : "";
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  $operation = $_POST['operation'];
  $json = isset($_POST['json']) ? $_POST['json'] : "";
}

$invoice = new Invoice();
switch ($operation) {
  case "saveInvoice":
    echo $invoice->saveInvoice($json);
    break;
}


 

 products.php

<?php
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");

class Product
{
  function getAllProducts()
  {
    include "connection-pdo.php";

    $sql = "SELECT * FROM tblproducts ORDER BY product_name";
    $stmt = $conn->prepare($sql);
    $stmt->execute();
    $rs = $stmt->fetchAll(PDO::FETCH_ASSOC);

    return json_encode($rs);
  }
}

//submitted by the client - operation and json
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  $operation = $_GET['operation'];
  $json = isset($_GET['json']) ? $_GET['json'] : "";
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  $operation = $_POST['operation'];
  $json = isset($_POST['json']) ? $_POST['json'] : "";
}

$product = new Product();
switch ($operation) {
  case "getAllProducts":
    echo $product->getAllProducts($json);
    break;
}


 

 

 

 

 

 

WORKING WITH HEADER-DETAIL FORM IN VANILLA JAVASCRIPT WITH API/PHP AND MYSQL

invoice.html < html lang = "en" >   < head >     < meta charset = "UTF-8" />     < meta name = ...