Making Invoices with HTML


Making Invoices with HTML

Many times, i need to calculate a budget for materials of a project. Other times, i need a quick way to make a simple invoice, for my clients, that can build on the go, from any device. No need for a database, to keep values, prices etc. Just a simple form, to write stuff, insert price and quantity and get the total. For that reasons, i build this HTML/Javascript script. It's quick , elegant and can be used from any system. You can have it online on a server of yours, or just locally on any device you use. Just open the program in a browser, insert the data and do whatever you want.

The program can print the form, so that you can export the form in any format your device supports, for example a PDF. You could also just take a screen shot of the device screen and send it to a customer/friend. It's "keyboard friendly" meaning, that you can use the whole form, without the need to touch the mouse, just press ENTER to move to the next field and TAB to switch between buttons and fields. At the same time, is also usable with touchscreen devices, as smartphones and tablets, so you can use it in any system/device, as the whole page is responsive.

Pressing the print button, it will create a new windows in your browser, displaying the data you entered, containing only text. I made it, this way, so i can either print/export it to PDF files, or just copy/paste the data, to any other application, like email, instant messaging apps etc. Simple ASCII text, is the way to export or even save your data, as you can easily convert it to any type you like, after.

invoice-template

The complete script is here, just save it to a file and open it with your preferred browser.

<!DOCTYPE html>
<!--
License: GPL-3.0-or-later

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Author: XQTR
Email: xqtr@gmx.com // xqtr.xqtr@gmail.com
Wensite: https://cp737.net
GitHub: https://github.com/xqtr/
Created: 2024/12/09
-->

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoice Generator</title>
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            background-color: #f9f9f9;
            margin: 0;
            padding: 20px;
            color: #333;
        }
        .container {
            max-width: 800px;
            margin: auto;
            background-color: #ffffff;
            border-radius: 12px;
            box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }
        header {
            background-color: #7F7F7F;  /* Match the header background color */
            color: white;  /* White text for contrast */
            padding: 20px;
        }

        header table {
            width: 100%;
            text-align: left;
            border-collapse: collapse;
            background-color: #7F7F7F;  /* Same background color as the header */
        }
        header th {
          background-color: #7F7F7F;  /* Same background color as the header */
        }
        header h1 {
            margin: 0;
            font-size: 24px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 0;
        }
        th, td {
            padding: 12px;
            text-align: left;
        }
        th {
            background-color: #eeeeee;
            color: #333;
            font-weight: bold;
        }
        td input {
            width: 100%;
            padding: 10px;
            font-size: 14px;
            border: 2px solid #ddd;
            border-radius: 8px;
            box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
            box-sizing: border-box;
            transition: border-color 0.2s, box-shadow 0.2s;
        }
        td input:focus {
            border-color: #6200ea;
            box-shadow: 0 0 6px rgba(98, 0, 234, 0.5);
            outline: none;
        }
        td span {
            font-weight: bold;
        }
        td button {
            background-color: #FF0000;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 6px 10px;
            cursor: pointer;
        }
        td button:hover {
            background-color: #e53935;
        }
        .add-item {
            display: block;
            width: 100%;
            padding: 15px;
            background-color: #1E90FF;
            color: white;
            text-align: center;
            text-decoration: none;
            border: none;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
        }
        .add-item:hover {
            background-color: #ADD8E6;
        }
        .summary {
            padding: 10px;
            background-color: #f5f5f5;
        }
        .summary p {
              margin: 4px 0; /* Reduce the margin between lines */
              line-height: 1.2; /* Adjust line spacing */
          }
        .summary table td {
            padding: 4px 8px; /* Reduce vertical and horizontal padding */
            line-height: 1.2; /* Adjust line spacing */
        }
        .summary label {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
            font-size: 14px;
        }
        .summary input {
            width: 60px;
            padding: 10px;
            font-size: 14px;
            border: 2px solid #ddd;
            border-radius: 8px;
            box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
            transition: border-color 0.2s, box-shadow 0.2s;
        }
        .summary input:focus {
            border-color: #6200ea;
            box-shadow: 0 0 6px rgba(98, 0, 234, 0.5);
            outline: none;
        }
        .totals {
            margin-top: 20px;
            text-align: right;
        }
        .totals strong {
            display: block;
            margin-bottom: 10px;
            font-size: 16px;
        }
        .totals table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 5px;
            font-size: 14px;
        }

        .totals td {
            padding: 2px 0;
            font-size: 16px;
            color: #333;
        }

        .totals td:first-child {
            font-weight: normal;
            text-align: left;
        }

        .totals td:last-child {
            font-weight: normal;
            text-align: right;
        }
        footer {
            text-align: center;
            padding: 10px;
            background-color: #4D4D4D;
            color: white;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
          <table style="width: 100%; text-align: left; border-collapse: collapse; background-color: #7F7F7F;">
            <tr>
              <th style="padding: 10px; color: white;"><h1>Invoice</h1></th>
              <th style="padding: 10px; text-align: right; color: white;"><p id="currentDate"></p></th>
            </tr>
          </table>
        </header>
        <table id="invoiceTable">
            <thead>
                <tr>
                   <th>Description</th>
                    <th>Quantity</th>
                    <th>Price</th>
                    <th>Total</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
        <button class="add-item" id="addItemBtn" onclick="addRow()">Add Item</button>

        <div class="summary">
            <label for="discount">Discount (%): <input type="number" id="discount" value="0" onchange="calculateTotals()" onfocus="selectValue(this)" onkeydown="handleEnter(event)"></label>
            <label for="vat">VAT (%): <input type="number" id="vat" value="0" onchange="calculateTotals()" onfocus="selectValue(this)" onkeydown="handleEnter(event)"></label>
        </div>
           <div class="summary">
                <table>
                    <tr>
                        <td>Subtotal:</td>
                        <td style="text-align: right;"><span id="subtotal">0.00</span></td>
                    </tr>
                    <tr>
                        <td>Discount:</td>
                        <td style="text-align: right;"><span id="discountAmount">0.00</span></td>
                    </tr>
                    <tr>
                        <td>NET Amount</td>
                        <td style="text-align: right;"><span id="netTotal">0.00</span></td>
                    </tr>
                    <tr>
                        <td>Taxes</td>
                        <td style="text-align: right;"><span id="vatAmount">0.00</span></td>
                    </tr>
                    <tr>
                        <td><strong>Total:</strong></td>
                        <td style="text-align: right;"><strong><span id="grandTotal">0.00</span></strong></td>
                    </tr>
                </table>
            </div>
        <footer>
            <button onclick="printInvoice()">Print Invoice</button>
        </footer>
    </div>

    <script>
        function printInvoice() {
    // Gather the details of the invoice as text
    let invoiceText = "Details  ::  Date: " + new Date().toLocaleDateString() + "\n\n";

    // Loop through each row in the table and get the details
    const rows = document.querySelectorAll('#invoiceTable tbody tr');

    invoiceText += `Description                     `;
    invoiceText += `Quantity `;
    invoiceText += `    Price    `;
    invoiceText += ` Total`;
    invoiceText += `\n--------------------------------------------------------------------------------\n`;

    rows.forEach(row => {
        const description = row.cells[0].querySelector('input').value || "N/A";
        const quantity = row.cells[1].querySelector('input').value || 0;
        const price = row.cells[2].querySelector('input').value || 0;
        const total = row.cells[3].querySelector('span').textContent || 0;

        invoiceText += `${description.padEnd(30, ' ')}`;
        invoiceText += `${parseFloat(quantity).toFixed(2).padStart(10, ' ')}`;
        invoiceText += `${parseFloat(price).toFixed(2).padStart(10, ' ')}`;
        invoiceText += `${parseFloat(total).toFixed(2).padStart(10, ' ')}\n`;
    });

    // Add subtotal, discount, VAT, and total
    const subtotal = document.getElementById('subtotal').textContent;
    const discountAmount = document.getElementById('discountAmount').textContent;
    const netTotal = document.getElementById('netTotal').textContent;
    const vatAmount = document.getElementById('vatAmount').textContent;
    const grandTotal = document.getElementById('grandTotal').textContent;

    invoiceText += `--------------------------------------------------------------------------------\n`;
    invoiceText += `Subtotal : ${subtotal.padStart(10, ' ')}\n`;
    invoiceText += `Discount : ${discountAmount.padStart(10, ' ')}\n`;
    invoiceText += `Net      : ${netTotal.padStart(10, ' ')}\n`;
    invoiceText += `VAT      : ${vatAmount.padStart(10, ' ')}\n`;
    invoiceText += `Total    : ${grandTotal.padStart(10, ' ')}\n`;

    // Open a new window for the printable content
    const printWindow = window.open('', '', 'height=600,width=800');

    // Write the content into the new window

    printWindow.document.write('<html><head>');
    printWindow.document.write(`
        <style>
            body {
                font-family: Arial, sans-serif;
                line-height: 1.6;
                margin: 20px;
            }
            pre {
                font-size: 14px;
                white-space: pre-wrap;
            }
        </style>
    `);  
    printWindow.document.write('<title>Invoice</title></head><body>');
    printWindow.document.write('<pre>' + invoiceText + '</pre>');
    printWindow.document.write('</body></html>');
    printWindow.document.close(); // Close the document for printing

    // Trigger the print dialog after content is loaded
    // printWindow.print();
}

        function formatToTwoDecimals(input) {
            const value = parseFloat(input.value) || 0; // Parse the value or default to 0
            input.value = value.toFixed(2); // Format to 2 decimal places
        }

        function addRow() {
            const table = document.getElementById('invoiceTable').getElementsByTagName('tbody')[0];
            const row = table.insertRow();
            row.innerHTML = `
                <td><input type="text" placeholder="Product Description" onfocus="selectValue(this)" onkeydown="handleEnter(event)"></td>
                <td><input type="number" value="1" min="1" onchange="updateRow(this)" step="1.00" onblur="formatToTwoDecimals(this)" onfocus="selectValue(this)" onkeydown="handleEnter(event)"></td>
                <td><input type="number" value="0" min="0" step="0.01" onchange="updateRow(this)" step="0.01" onblur="formatToTwoDecimals(this)" onfocus="selectValue(this)" onkeydown="handleEnter(event)"></td>
                <td style="text-align: right;"><span>0.00</span></td>
                <td><button onclick="removeRow(this)">Remove</button></td>
            `;
            // Focus on the description field of the new row
            row.cells[0].querySelector('input').focus();
        }

        function updateRow(input) {
            const row = input.closest('tr');
            const quantity = row.cells[1].querySelector('input').value || 0;
            const price = row.cells[2].querySelector('input').value || 0;
            const total = (quantity * price).toFixed(2);
            row.cells[3].querySelector('span').textContent = total;
            calculateTotals();
        }

        function removeRow(button) {
            button.closest('tr').remove();
            calculateTotals();
        }

        function calculateTotals() {
            let subtotal = 0;
            const rows = document.querySelectorAll('#invoiceTable tbody tr');
            rows.forEach(row => {
                const total = parseFloat(row.cells[3].querySelector('span').textContent) || 0;
                subtotal += total;
            });

            const discount = parseFloat(document.getElementById('discount').value) || 0;
            const vat = parseFloat(document.getElementById('vat').value) || 0;

            const discountAmount = (subtotal * discount / 100).toFixed(2);
            const netTotal = (subtotal - discountAmount).toFixed(2);
            const vatAmount = ((subtotal - discountAmount) * vat / 100).toFixed(2);
            const grandTotal = (subtotal - discountAmount + parseFloat(vatAmount)).toFixed(2);

            document.getElementById('subtotal').textContent = subtotal.toFixed(2);
            document.getElementById('discountAmount').textContent = discountAmount;
            document.getElementById('netTotal').textContent = netTotal;
            document.getElementById('vatAmount').textContent = vatAmount;
            document.getElementById('grandTotal').textContent = grandTotal;
        }

        function selectValue(input) {
            input.select();
        }

        // Handle the Enter key
        function handleEnter(event) {
            if (event.key === "Enter") {
                const input = event.target;
                const row = input.closest('tr');
                const inputs = Array.from(row.querySelectorAll('input'));
                const currentIndex = inputs.indexOf(input);

                if (currentIndex < inputs.length - 1) {
                    // Move to the next input in the row
                    inputs[currentIndex + 1].focus();
                } else {
                    // Add a new row and focus on its description field
                    addRow();
                }

                event.preventDefault(); // Prevent the default Enter key behavior
            }
        }

        document.addEventListener("DOMContentLoaded", () => {
    // Add a new row when the page loads
    addRow();

    // Set the current date
    const currentDateElement = document.getElementById("currentDate");
    const currentDate = new Date();
    const options = { year: 'numeric', month: 'long', day: 'numeric' }; // Format: Month Day, Year
    currentDateElement.textContent = `${currentDate.toLocaleDateString(undefined, options)}`;
});
    </script>    
</body>
</html>

Add a comment

Previous Next