Wednesday, 28 January 2026

Estimator ( Cost Estimator)

 <?php

// ------------------- PHP SECTION -------------------

// Load CSV into array

function loadCSV($filename) {

    $rows = array_map('str_getcsv', file($filename));

    $header = array_shift($rows);

    $csv = [];

    foreach ($rows as $row) {

        $csv[] = array_combine($header, $row);

    }

    return $csv;

}


$data = loadCSV("estimator.csv");


function loadXML($filename) {

    $xml = simplexml_load_file($filename) or die("Unable to load XML file");

    $data = [];


    foreach ($xml->Estimator as $est) {

        $data[] = [

            "Techstack"   => trim((string)$est->Techstack),

            "Platform"    => trim((string)$est->Platform),

            "Env"         => trim((string)$est->Env),

            "T-Shirt Size"=> trim((string)$est->TShirtSize),

            "Compute"     => trim((string)$est->Compute),

            "License"     => trim((string)$est->License),

            "Input Notes" => trim((string)$est->InputNotes)

        ];

    }

    return $data;

}


//$data = loadXML("estimator.xml");



// Handle AJAX requests


if (isset($_GET['action'])) {

    header('Content-Type: application/json');

    $action = $_GET['action'];


    // Techstack

/*

    if ($action == 'getTechstack') {

        $techs = [];

        foreach ($data as $row) {

            if (!empty(trim($row['Techstack']))) {

                $techs[] = trim($row['Techstack']);

            }

        }

        echo json_encode(array_values(array_unique($techs)));

        exit;

    }

*/

if ($action == 'getTechstack') {

    $techs = [];

    foreach ($data as $row) {

        if (!empty($row['Techstack'])) {

            $techs[] = $row['Techstack'];

        }

    }

    echo json_encode(array_values(array_unique($techs)));

    exit;

}


    // Platform

    if ($action == 'getPlatform' && !empty($_GET['tech'])) {

        $platforms = [];

        foreach ($data as $row) {

            if (trim($row['Techstack']) === trim($_GET['tech'])) {

                $platforms[] = trim($row['Platform']);

            }

        }

        echo json_encode(array_values(array_unique($platforms)));

        exit;

    }


    // Env

    if ($action == 'getEnv' && !empty($_GET['tech']) && !empty($_GET['platform'])) {

        $envs = [];

        foreach ($data as $row) {

            if (trim($row['Techstack']) === trim($_GET['tech']) &&

                trim($row['Platform']) === trim($_GET['platform'])) {

                $envs[] = trim($row['Env']);

            }

        }

        echo json_encode(array_values(array_unique($envs)));

        exit;

    }


    // T-Shirt Size

    if ($action == 'getSize' && !empty($_GET['tech']) && !empty($_GET['platform']) && !empty($_GET['env'])) {

        $sizes = [];

        foreach ($data as $row) {

            if (trim($row['Techstack']) === trim($_GET['tech']) &&

                trim($row['Platform']) === trim($_GET['platform']) &&

                trim($row['Env']) === trim($_GET['env'])) {

                $sizes[] = trim($row['T-Shirt Size']);

            }

        }

        echo json_encode(array_values(array_unique($sizes)));

        exit;

    }


    // Details (Compute, License, Notes)

    if ($action == 'getDetails' && !empty($_GET['tech']) && !empty($_GET['platform']) && !empty($_GET['env']) && !empty($_GET['size'])) {

        foreach ($data as $row) {

            if (trim($row['Techstack']) === trim($_GET['tech']) &&

                trim($row['Platform']) === trim($_GET['platform']) &&

                trim($row['Env']) === trim($_GET['env']) &&

                trim($row['T-Shirt Size']) === trim($_GET['size'])) {

                echo json_encode([

                    "compute" => trim($row['Compute']),

                    "license" => trim($row['License']),

                    "notes"   => trim($row['Input Notes'])

                ]);

                exit;

            }

        }

    }

}



?>


<!DOCTYPE html>

<html>

<head>

    <title>Estimator</title>

    <style>

        body { font-family: Arial, sans-serif; background: #f4f6f9; }

        table { border-collapse: collapse; width: 100%; margin: 20px 0; }

        th, td { border: 1px solid #ddd; padding: 8px; text-align: center; }

        th { background: #007acc; color: white; }

        tr:nth-child(even) { background: #f9f9f9; }

        button { background: #007acc; color: white; border: none; padding: 6px 12px; cursor: pointer; }

        button:hover { background: #005f99; }

        #grandTotal { font-weight: bold; margin-top: 20px; }

/* Grand Total row styling */

.grand-total {

    background-color: #f4f6f9;   /* subtle light gray background */

    font-weight: bold;           /* bold text */

    color: #2c3e50;              /* dark slate text */

    border-top: 2px solid #34495e; /* strong top border */

}


.grand-total td {

    padding: 8px 12px;           /* consistent spacing */

    text-align: center;          /* center align numbers */

}


.grand-total td:first-child {

    text-align: left;            /* label aligned left */

    font-size: 1.1em;            /* slightly larger font */

    color: #1a5276;              /* accent color for label */

}


/* CAPEX Table Styling */

#capexTable {

    border-collapse: collapse;

    width: 100%;

    margin-top: 20px;

    font-family: Arial, sans-serif;

    font-size: 14px;

}


#capexTable th, #capexTable td {

    border: 1px solid #ccc;

    padding: 8px 12px;

    text-align: center;

}


#capexTable th {

    background-color: #2c3e50;   /* dark header */

    color: #fff;                 /* white text */

    font-weight: bold;

}


#capexTable tr:nth-child(even) {

    background-color: #f9f9f9;   /* zebra striping */

}


#capexTable tr:hover {

    background-color: #eef;      /* subtle hover effect */

}


#capexTable .grand-total {

    background-color: #f4f6f9;   /* light gray background */

    font-weight: bold;

    border-top: 2px solid #34495e;

}


#capexTable .grand-total td {

    color: #1a5276;              /* accent color for totals */

    font-size: 1.05em;

}

/* CAPEX Table Styling */

#capexTable {

    border-collapse: collapse;

    width: 100%;

    margin-top: 20px;

    font-family: Arial, sans-serif;

    font-size: 14px;

}


#capexTable th, #capexTable td {

    border: 1px solid #ccc;

    padding: 10px 14px;

    text-align: center;

}


#capexTable th {

    background-color: #2c3e50;   /* dark header */

    color: #fff;                 /* white text */

    font-weight: bold;

    text-transform: uppercase;

}


#capexTable tr:nth-child(even) {

    background-color: #f9f9f9;   /* zebra striping */

}


#capexTable tr:hover {

    background-color: #eef;      /* subtle hover effect */

}


#capexTable .grand-total {

    background-color: #f4f6f9;   /* light gray background */

    font-weight: bold;

    border-top: 2px solid #34495e;

}


#capexTable .grand-total td {

    color: #1a5276;              /* accent color for totals */

    font-size: 1.05em;

}


    </style>

</head>

<body>


<h2>Estimator Table</h2>

<button onclick="addRow()">Add Row</button>

<button onclick="exportCSV()">Export to CSV</button>


<table id="estimatorTable">

    <tr>

        <th>Techstack</th>

        <th>Platform</th>

        <th>Env</th>

        <th>T-Shirt Size</th>

        <th>Compute</th>

        <th>License</th>

        <th>Units/Cluster</th>

        <th>Input Notes</th>

        <th>User Notes</th>

        <th>Total Cost/Year</th>

        <th>Delete</th>

    </tr>

</table>



<div id="grandTotal">Grand Total: 0 | HW: 0 | SW: 0</div>


<h3>5-Year CAPEX</h3>

<table id="capexTable">

    <tr><th>Year</th><th>HW Cost</th><th>SW Cost</th><th>Total</th></tr>

</table>


<script>

let table = document.getElementById("estimatorTable");

let grandTotal = document.getElementById("grandTotal");


console.log("Platforms for", tech, data);


function addRow() {

    let row = table.insertRow();

    // Techstack dropdown

    let techCell = row.insertCell();

    let techSelect = document.createElement("select");

    techSelect.onchange = () => {

    if (techSelect.value) {

        loadPlatform(row, techSelect.value);

    }

};


function updateCapexTable(hwCosts, swCosts) {

    let tbody = document.getElementById("capexBody");

    tbody.innerHTML = "";


    // HW row

    let hwRow = tbody.insertRow();

    hwRow.innerHTML = `

        <td>HW Cost</td>

        <td>${hwCosts[0]}</td>

        <td>${hwCosts[1]}</td>

        <td>${hwCosts[2]}</td>

        <td>${hwCosts[3]}</td>

        <td>${hwCosts[4]}</td>

        <td>${hwCosts.reduce((a,b)=>a+b,0)}</td>

    `;


    // SW row

    let swRow = tbody.insertRow();

    swRow.innerHTML = `

        <td>SW Cost</td>

        <td>${swCosts[0]}</td>

        <td>${swCosts[1]}</td>

        <td>${swCosts[2]}</td>

        <td>${swCosts[3]}</td>

        <td>${swCosts[4]}</td>

        <td>${swCosts.reduce((a,b)=>a+b,0)}</td>

    `;


    // Total row

    let totalRow = tbody.insertRow();

    totalRow.classList.add("grand-total"); // <-- reapply CSS class

    totalRow.innerHTML = `

        <td>Total</td>

        <td>${hwCosts[0]+swCosts[0]}</td>

        <td>${hwCosts[1]+swCosts[1]}</td>

        <td>${hwCosts[2]+swCosts[2]}</td>

        <td>${hwCosts[3]+swCosts[3]}</td>

        <td>${hwCosts[4]+swCosts[4]}</td>

        <td><b>${hwCosts.reduce((a,b)=>a+b,0)+swCosts.reduce((a,b)=>a+b,0)}</b></td>

    `;

}



// After populating options, trigger once

techSelect.selectedIndex = 0; // or 1 if you want first real value

techSelect.dispatchEvent(new Event("change"));




    techCell.appendChild(techSelect);


    // Platform dropdown

    let platCell = row.insertCell();

    let platSelect = document.createElement("select");

    platSelect.onchange = () => loadEnv(row, techSelect.value, platSelect.value);

    platCell.appendChild(platSelect);


    // Env dropdown

    let envCell = row.insertCell();

    let envSelect = document.createElement("select");

    envSelect.onchange = () => loadSize(row, techSelect.value, platSelect.value, envSelect.value);

    envCell.appendChild(envSelect);


    // Size dropdown

    let sizeCell = row.insertCell();

    let sizeSelect = document.createElement("select");

    sizeSelect.onchange = () => loadDetails(row, techSelect.value, platSelect.value, envSelect.value, sizeSelect.value);

    sizeCell.appendChild(sizeSelect);


    // Compute

    let computeCell = row.insertCell();

    computeCell.innerHTML = "0";


    // License

    let licenseCell = row.insertCell();

    licenseCell.innerHTML = "0";


    // Units

    let unitCell = row.insertCell();

    let unitInput = document.createElement("input");

    unitInput.type = "number"; unitInput.value = 1;

    unitInput.oninput = () => calculateRow(row);

    unitCell.appendChild(unitInput);


    // Input Notes

    let notesCell = row.insertCell();

    notesCell.innerHTML = "";


    // User Notes

    let userNotesCell = row.insertCell();

    let userNotesInput = document.createElement("input");

    userNotesInput.type = "text";

    userNotesCell.appendChild(userNotesInput);


    // Total Cost

    let costCell = row.insertCell();

    costCell.innerHTML = "0";


    // Delete

    let delCell = row.insertCell();

    let delBtn = document.createElement("button");

    delBtn.innerHTML = "Delete";

    delBtn.onclick = () => { table.deleteRow(row.rowIndex); updateTotals(); };

    delCell.appendChild(delBtn);


    // Load Techstack options

 fetch("?action=getTechstack")

    .then(res => res.json())

    .then(data => {

        techSelect.innerHTML = "";

        let defaultOpt = document.createElement("option");

        defaultOpt.value = "";

        defaultOpt.text = "-- Select Techstack --";

        techSelect.add(defaultOpt);


        data.forEach(val => {

            let opt = document.createElement("option");

            opt.value = val;

            opt.text = val;

            techSelect.add(opt);

        });

    });

//updateGrandTotal();

}


function loadPlatform1(row, tech) {

    fetch("?action=getPlatform&tech="+tech)

        .then(res => res.json())

        .then(data => {

            let platSelect = row.cells[1].children[0];

            platSelect.innerHTML = "";

            data.forEach(val => {

                let opt = document.createElement("option");

                opt.value = val; opt.text = val;

                platSelect.add(opt);

            });

        });

}


function loadPlatform(row, tech) {

    fetch("?action=getPlatform&tech=" + encodeURIComponent(tech))

        .then(res => res.json())

        .then(data => {

            let platSelect = row.cells[1].children[0];

            platSelect.innerHTML = "";


            // Add default option

            let defaultOpt = document.createElement("option");

            defaultOpt.value = "";

            defaultOpt.text = "-- Select Platform --";

            platSelect.add(defaultOpt);


            data.forEach(val => {

                let opt = document.createElement("option");

                opt.value = val;

                opt.text = val;

                platSelect.add(opt);

            });

        });

}



function loadEnv(row, tech, platform) {

    fetch("?action=getEnv&tech=" + encodeURIComponent(tech) + "&platform=" + encodeURIComponent(platform))

        .then(res => res.json())

        .then(data => {

            let envSelect = row.cells[2].children[0];

            envSelect.innerHTML = "";


            let defaultOpt = document.createElement("option");

            defaultOpt.value = "";

            defaultOpt.text = "-- Select Env --";

            envSelect.add(defaultOpt);


            data.forEach(val => {

                let opt = document.createElement("option");

                opt.value = val;

                opt.text = val;

                envSelect.add(opt);

            });

        });

}


function loadSize(row, tech, platform, env) {

    fetch("?action=getSize&tech=" + encodeURIComponent(tech) + "&platform=" + encodeURIComponent(platform) + "&env=" + encodeURIComponent(env))

        .then(res => res.json())

        .then(data => {

            let sizeSelect = row.cells[3].children[0];

            sizeSelect.innerHTML = "";


            let defaultOpt = document.createElement("option");

            defaultOpt.value = "";

            defaultOpt.text = "-- Select Size --";

            sizeSelect.add(defaultOpt);


            data.forEach(val => {

                let opt = document.createElement("option");

                opt.value = val;

                opt.text = val;

                sizeSelect.add(opt);

            });

        });

}


function loadDetails(row, tech, platform, env, size) {

    fetch("?action=getDetails&tech="+tech+"&platform="+platform+"&env="+env+"&size="+size)

        .then(res => res.json())

        .then(data => {

            row.cells[4].innerHTML = data.compute;

            row.cells[5].innerHTML = data.license;

            row.cells[7].innerHTML = data.notes;

            row.dataset.compute = data.compute;

            row.dataset.license = data.license;

            calculateRow(row);

        });

}


// ------------------- CALCULATIONS -------------------

function calculateRow(row) {

    let compute = parseFloat(row.dataset.compute || 0);

    let license = parseFloat(row.dataset.license || 0);

    let units = parseInt(row.cells[6].children[0].value || 1);

    let total = (compute + license) * units;

    row.cells[9].innerHTML = total.toFixed(2);

    updateTotals();

//updateGrandTotal();

}


function updateTotals() {

    let total = 0, hw = 0, sw = 0;

    for(let i=1; i<table.rows.length; i++) {

        let row = table.rows[i];

        let units = parseInt(row.cells[6].children[0].value || 1);

        let compute = parseFloat(row.dataset.compute || 0);

        let license = parseFloat(row.dataset.license || 0);

        total += parseFloat(row.cells[9].innerHTML || 0);

        hw += compute * units;

        sw += license * units;

    }

    grandTotal.innerHTML = `Grand Total: ${total.toFixed(0)} | HW: ${hw.toFixed(0)} | SW: ${sw.toFixed(0)}`;

    generateCAPEX(hw, sw);

}


// ------------------- CAPEX -------------------

function generateCAPEX(hw, sw) {

    let capexTable = document.getElementById("capexTable");

    capexTable.innerHTML = "";


    // Header row: Years

    let headerRow = capexTable.insertRow();

    headerRow.insertCell().innerHTML = "Cost Type";

    for (let year = 1; year <= 5; year++) {

        headerRow.insertCell().innerHTML = `Year ${year}`;

    }

    headerRow.insertCell().innerHTML = "Grand Total (5 yrs)";


    // HW row

    let hwRow = capexTable.insertRow();

    hwRow.insertCell().innerHTML = "HW Cost";

    for (let year = 1; year <= 5; year++) {

        hwRow.insertCell().innerHTML = hw.toFixed(0);

    }

    hwRow.insertCell().innerHTML = (hw * 5).toFixed(0);


    // SW row

    let swRow = capexTable.insertRow();

    swRow.insertCell().innerHTML = "SW Cost";

    for (let year = 1; year <= 5; year++) {

        swRow.insertCell().innerHTML = sw.toFixed(0);

    }

    swRow.insertCell().innerHTML = (sw * 5).toFixed(0);


    // Total row

    let totalRow = capexTable.insertRow();

    totalRow.insertCell().innerHTML = "Total";

    for (let year = 1; year <= 5; year++) {

        totalRow.insertCell().innerHTML = (hw + sw).toFixed(0);

    }

    totalRow.insertCell().innerHTML = ((hw + sw) * 5).toFixed(0);

}


// ------------------- EXPORT TO CSV -------------------


function exportCSV() {

    let csv = [];


    // ---------------- Estimator Table ----------------

    let estTable = document.getElementById("estimatorTable");

    let estRows = estTable.querySelectorAll("tr");


    estRows.forEach(row => {

        let cols = row.querySelectorAll("th, td");

        let rowData = [];

        cols.forEach((col, idx) => {

            // Skip the last column (Delete button)

            if (idx === cols.length - 1) return;


            let text = "";


            // Handle dropdowns

            if (col.querySelector("select")) {

                let sel = col.querySelector("select");

                text = sel.options[sel.selectedIndex]?.text || "";

            }

            // Handle inputs

            else if (col.querySelector("input")) {

                text = col.querySelector("input").value;

            }

            // Otherwise plain text

            else {

                text = col.innerText.trim();

            }


            text = '"' + text.replace(/"/g, '""') + '"';

            rowData.push(text);

        });

        csv.push(rowData.join(","));

    });


    // ---------------- CAPEX Table ----------------

    csv.push(""); // Blank line separator

    csv.push("CAPEX Projection (Horizontal)");


    let capexTable = document.getElementById("capexTable");

    let capexRows = capexTable.querySelectorAll("tr");


    capexRows.forEach(row => {

        let cols = row.querySelectorAll("th, td");

        let rowData = [];

        cols.forEach(col => {

            let text = col.innerText.trim();

            text = '"' + text.replace(/"/g, '""') + '"';

            rowData.push(text);

        });

        csv.push(rowData.join(","));

    });


    // ---------------- Branded Footer ----------------

    // csv.push("");

    // csv.push('"Developed by Dilip"');


    // ---------------- Download ----------------

    let csvString = csv.join("\n");

    let blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });

    let link = document.createElement("a");

    link.href = URL.createObjectURL(blob);

    link.download = "estimator_with_capex.csv";

    document.body.appendChild(link);

    link.click();

    document.body.removeChild(link);

}


function updateGrandTotal() {

    let table = document.getElementById("estimatorTable");

    let rows = table.querySelectorAll("tr");


    let hwSum = 0, swSum = 0;


    // Loop through rows to accumulate HW and SW

    rows.forEach((row, idx) => {

        if (idx === 0) return; // skip header

        let computeCell = row.cells[4]; // HW

        let licenseCell = row.cells[5]; // SW


        let hwVal = parseFloat(computeCell.innerText || computeCell.querySelector("input")?.value || 0);

        let swVal = parseFloat(licenseCell.innerText || licenseCell.querySelector("input")?.value || 0);


        hwSum += hwVal || 0;

        swSum += swVal || 0;

    });


    // Remove old Grand Total row if exists

    let lastRow = table.rows[table.rows.length - 1];

    if (lastRow && lastRow.classList.contains("grand-total")) {

        table.deleteRow(table.rows.length - 1);

    }


    // Add new Grand Total row

    let totalRow = table.insertRow();

    totalRow.classList.add("grand-total");


    let cell = totalRow.insertCell();

    cell.colSpan = 4;

    cell.innerHTML = "<b>Grand Total</b>";


    let hwCell = totalRow.insertCell();

    hwCell.innerHTML = hwSum.toFixed(2);


    let swCell = totalRow.insertCell();

    swCell.innerHTML = swSum.toFixed(2);


    let totalCell = totalRow.insertCell();

    totalCell.colSpan = 4;

    totalCell.innerHTML = "<b>" + (hwSum + swSum).toFixed(2) + "</b>";

}



</script>

</body>

</html>

No comments:

Post a Comment