Monday, 16 February 2026

Update capex

function readComputeLicenceTotals() {

  console.clear(); // Clear console

  

  const table = document.querySelector('table') || document.getElementById('yourTableId');

  console.log('🔍 Table:', table ? '✅ Found' : '❌ Missing');

  

  if (!table || table.rows.length === 0) return {compute: 0, licence: 0, grand: 0};

  

  // DEBUG: Show ALL headers + first row

  const headers = Array.from(table.rows[0].cells).map((cell, i) => 

    `Col${i}: "${cell.textContent.trim()} | ${cell.innerHTML.substring(0,50)}..."`

  );

  console.table(['Headers:', headers.join('

')]);

  

  const firstRowCells = table.rows[1]?.cells;

  if (firstRowCells) {

    console.table(['Sample Row:', Array.from(firstRowCells).map((cell, i) => 

      `Col${i}: "${cell.textContent.trim()} | HTML: ${cell.innerHTML.substring(0,30)}..."`

    )]);

  }

  

  // UNIVERSAL: Find columns by ANY mention of compute/licence

  let computeCol = -1, licenceCol = -1;

  for (let col = 0; col < table.rows[0].cells.length; col++) {

    const headerText = table.rows[0].cells[col].textContent.toLowerCase();

    const headerHTML = table.rows[0].cells[col].innerHTML.toLowerCase();

    

    if (headerText.includes('compute') || headerHTML.includes('compute')) computeCol = col;

    if (headerText.includes('licence') || headerText.includes('license') || headerHTML.includes('licence')) licenceCol = col;

  }

  

  console.log(`📊 Compute Col: ${computeCol} | Licence Col: ${licenceCol}`);

  

  if (computeCol === -1 || licenceCol === -1) {

    console.error('❌ Columns not found! Check headers above.');

    return {compute: 0, licence: 0, grand: 0};

  }

  

  // READ ALL ROWS - Handles text, inputs, spans, autopopulated

  let computeTotal = 0, licenceTotal = 0;

  for (let r = 1; r < table.rows.length; r++) { // Skip header

    const row = table.rows[r];

    

    // COMPUTE CELL - Try all methods

    if (row.cells[computeCol]) {

      const cell = row.cells[computeCol];

      let val = 0;

      

      // Method 1: Direct text

      val = parseFloat(cell.textContent.replace(/[^d.,-]/g, '').replace(/,/g, '')) || 0;

      

      // Method 2: Input value (autopopulated inputs)

      const input = cell.querySelector('input[type="number"], input[type="text"]');

      if (input) val = parseFloat(input.value) || val;

      

      // Method 3: Span/div autopopulated

      const span = cell.querySelector('span, div');

      if (span && !input) val = parseFloat(span.textContent.replace(/[^d.,-]/g, '').replace(/,/g, '')) || val;

      

      computeTotal += val;

      console.log(`Row ${r} Compute: "${cell.innerHTML}" → ${val}`);

    }

    

    // LICENCE CELL - Same logic

    if (row.cells[licenceCol]) {

      const cell = row.cells[licenceCol];

      let val = 0;

      

      val = parseFloat(cell.textContent.replace(/[^d.,-]/g, '').replace(/,/g, '')) || 0;

      const input = cell.querySelector('input');

      if (input) val = parseFloat(input.value) || val;

      const span = cell.querySelector('span, div');

      if (span && !input) val = parseFloat(span.textContent.replace(/[^d.,-]/g, '').replace(/,/g, '')) || val;

      

      licenceTotal += val;

      console.log(`Row ${r} Licence: "${cell.innerHTML}" → ${val}`);

    }

  }

  

  const grandTotal = computeTotal + licenceTotal;

  

  console.log(`✅ FINALS: Compute: ${computeTotal} | Licence: ${licenceTotal} | Grand: ${grandTotal}`);

  

  // Set globals for capex table

  window.computeTotalForCapex = computeTotal;

  window.licenceTotalForCapex = licenceTotal;

  window.grandTotalForCapex = grandTotal;

  

  return {compute: computeTotal, licence: licenceTotal, grand: grandTotal};

}



function readComputeLicenceTotals1() {

  const table = document.getElementById('yourMainTable') || document.querySelector('table');

  if (!table) {

    console.error('No table found!');

    return {compute: 0, licence: 0, grand: 0};

  }

  

  // Find column indices by HEADER names (case-insensitive)

  const headers = Array.from(table.rows[0]?.cells || []);

  const computeCol = headers.findIndex(h => 

    h.textContent.toLowerCase().includes('compute')

  );

  const licenceCol = headers.findIndex(h => 

    h.textContent.toLowerCase().includes('licence') || 

    h.textContent.toLowerCase().includes('license')

  );

  

  console.log('Compute column:', computeCol, headers[computeCol]?.textContent);

  console.log('Licence column:', licenceCol, headers[licenceCol]?.textContent);

  

  if (computeCol === -1 || licenceCol === -1) {

    console.error('Columns not found! Headers:', headers.map(h => h.textContent));

    return {compute: 0, licence: 0, grand: 0};

  }

  

  // Sum values from data rows

  let computeTotal = 0, licenceTotal = 0;

  const dataRows = table.rows.length > 1 ? table.rows.length - 1 : 0; // Skip header

  

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

    const row = table.rows[i];

    if (row.cells[computeCol]) {

      const val = parseFloat(row.cells[computeCol].textContent.replace(/[^d.,]/g, '').replace(/,/g, '')) || 0;

      computeTotal += val;

    }

    if (row.cells[licenceCol]) {

      const val = parseFloat(row.cells[licenceCol].textContent.replace(/[^d.,]/g, '').replace(/,/g, '')) || 0;

      licenceTotal += val;

    }

  }

  

  const grandTotal = computeTotal + licenceTotal;

  

  console.log('Totals:', {compute: computeTotal, licence: licenceTotal, grand: grandTotal});

  

  // Set globals

  window.computeTotalForCapex = computeTotal;

  window.licenceTotalForCapex = licenceTotal;

  window.grandTotalForCapex = grandTotal;

  

  return {compute: computeTotal, licence: licenceTotal, grand: grandTotal};

}


 function addRowAndUpdateTotal() {

  // ... your existing add row code ...

  

  // READ Licence & Compute COLUMNS (adjust column indices/selectors)

  const table = document.getElementById('yourMainTable'); // Your table ID

  const rows = table.querySelectorAll('tbody tr'); // Data rows only

  

  let computeTotal = 0;

  let licenceTotal = 0;

  

  rows.forEach(row => {

    // Assuming: Col 1=Item, Col 2=Compute, Col 3=Licence (adjust indices)

    const computeVal = parseFloat(row.cells[1]?.textContent.replace(/[^d.,]/g, '').replace(/,/g, '')) || 0;

    const licenceVal = parseFloat(row.cells[2]?.textContent.replace(/[^d.,]/g, '').replace(/,/g, '')) || 0;

    

    computeTotal += computeVal;

    licenceTotal += licenceVal;

  });

  

  const grandTotal = computeTotal + licenceTotal;

  

  // Update your table's grand total display (if needed)

  const grandCell = document.getElementById('grandTotalCell');

  if (grandCell) grandCell.textContent = grandTotal.toLocaleString('en-IN');

  

  // Share for capex table

  window.computeTotalForCapex = computeTotal;

  window.licenceTotalForCapex = licenceTotal;

  window.grandTotalForCapex = grandTotal;

  

  window.dispatchEvent(new CustomEvent('totalsUpdated'));

}


function addRowAndUpdateTotal() {

  // ... your existing add row code ...

  

  // SAFE grand total (adjust ID to match your table)

  const grandTotalCell = document.getElementById('grandTotalCell') || 

                        document.querySelector('#yourTable tfoot td:last-child');

  

  if (grandTotalCell) {

    const grandTotal = parseFloat(grandTotalCell.textContent.replace(


function updateCapexTable() {

  // SAFE CHECK - No more null errors

  const container = document.getElementById('capexContainer');

  if (!container) {

    console.error('Missing #capexContainer div!');

    return;

  }

  

  const year1Capex = window.grandTotalForCapex || 0;

  const growthRate = 0.08;

  

  let html = `

    <table id="capexTable" border="1" style="border-collapse: collapse; width: 100%; font-family: monospace;">

      <thead style="background: #e3f2fd;">

        <tr>

          <th>Particulars</th>

          <th>Grand Total<br>(Year 1)</th>

          <th>Year 2 (+8%)</th>

          <th>Year 3</th>

          <th>Year 4</th>

          <th>Year 5</th>

          <th style="background: #c8e6c9;">Cumulative</th>

        </tr>

      </thead>

      <tbody>

        <tr style="font-weight: bold;">

          <td>Capex (₹ Cr)</td>

          <td style="text-align: right;">${year1Capex.toLocaleString('en-IN')}</td>`;


  let cumulative = year1Capex;

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

    const capex = year1Capex * Math.pow(1 + growthRate, year - 1);

    cumulative += capex;

    html += `<td style="text-align: right;">${capex.toLocaleString('en-IN')}</td>`;

  }

  

  html += `<td style="text-align: right; font-size: 1.1em;">${cumulative.toLocaleString('en-IN')}</td></tr></tbody></table>`;

  

  container.innerHTML = html; // SAFE - container exists

}


// SAFE listeners

if (document.readyState === 'loading') {

  document.addEventListener('DOMContentLoaded', updateCapexTable);

} else {

  updateCapexTable();

}


window.addEventListener('grandTotalUpdated', updateCapexTable);


<!-- ADD THIS DIV - Required for capex table -->

<div id="capexContainer" style="margin: 20px 0;"></div>



function updateCapexTable1() {

  const year1Capex = window.grandTotalForCapex || 0;

  const growthRate = 0.08;  // Edit as needed

  

  let html = `

    <table id="capexTable" border="1" style="border-collapse: collapse; margin-top: 20px; font-family: monospace;">

      <thead style="background: #e3f2fd; font-weight: bold;">

        <tr>

          <th style="padding: 12px 8px;">Particulars</th>

          <th style="padding: 12px 8px;">Grand Total<br>(Year 1 Base)</th>

          <th style="padding: 12px 8px;">Year 2<br>(+8%)</th>

          <th style="padding: 12px 8px;">Year 3</th>

          <th style="padding: 12px 8px;">Year 4</th>

          <th style="padding: 12px 8px;">Year 5</th>

          <th style="padding: 12px 8px;">Cumulative<br>Total</th>

        </tr>

      </thead>

      <tbody>

        <tr style="background: #f8f9ff; font-weight: bold;">

          <td style="padding: 10px 8px;">Capex (₹ Cr)</td>

          <td style="text-align: right; padding: 10px 8px;">${year1Capex.toLocaleString('en-IN')}</td>`;


  let cumulative = year1Capex;

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

    const capex = year1Capex * Math.pow(1 + growthRate, year - 1);

    cumulative += capex;

    html += `<td style="text-align: right; padding: 10px 8px;">${capex.toLocaleString('en-IN')}</td>`;

  }

  

  html += `

          <td style="text-align: right; padding: 10px 8px; background: #c8e

  

  document.getElementById('capexContainer').innerHTML = html;

}


function updateCapexTable() {

  const container = document.getElementById('capexContainer');

  if (!container) return;

  

  const computeY1 = window.computeTotalForCapex || 0;

  const licenceY1 = window.licenceTotalForCapex || 0;

  const grandY1 = window.grandTotalForCapex || 0;

  const growthRate = 0.08;

  

  let html = `

    <table id="capexTable" border="1" style="border-collapse: collapse; width: 100%; margin-top: 20px;">

      <thead style="background: #e3f2fd; font-weight: bold;">

        <tr>

          <th style="padding: 12px 8px;">Particulars</th>

          <th>Year 1<br>(Base)</th><th>Year 2<br>(+8%)</th><th>Year 3</th><th>Year 4</th><th>Year 5</th><th style="background: #c8e6c9;">Cumulative</th>

        </tr>

      </thead>

      <tbody>

        <tr>

          <td style="padding: 10px; background: #fff3e0;">Compute</td>

          <td style="text-align: right; padding: 10px;">${computeY1.toLocaleString('en-IN')}</td>`;


  let computeCum = computeY1;

  for (let y = 2; y <= 5; y++) {

    const val = computeY1 * Math.pow(1 + growthRate, y - 1);

    computeCum += val;

    html += `<td style="text-align: right; padding: 10px;">${val.toLocaleString('en-IN')}</td>`;

  }

  html += `<td style="text-align: right; padding: 10px; background: #fff3e0;">${computeCum.toLocaleString('en-IN')}</td></tr>`;


  // Licence row

  html += `

        <tr>

          <td style="padding: 10px; background: #e8f5e8;">Licence</td>

          <td style="text-align: right; padding: 10px;">${licenceY1.toLocaleString('en-IN')}</td>`;


  let licenceCum = licenceY1;

  for (let y = 2; y <= 5; y++) {

    const val = licenceY1 * Math.pow(1 + growthRate, y - 1);

    licenceCum += val;

    html += `<td style="text-align: right; padding: 10px;">${val.toLocaleString('en-IN')}</td>`;

  }

  html += `<td style="text-align: right; padding: 10px; background: #e8f5e8;">${licenceCum.toLocaleString('en-IN')}</td></tr>`;


  // Grand Total row

  html += `

        <tr style="font-weight: bold; background: #f5f5f5;">

          <td style="padding: 10px;">Grand Total</td>

          <td style="text-align: right; padding: 10px;">${grandY1.toLocaleString('en-IN')}</td>`;


  let grandCum = grandY1;

  for (let y = 2; y <= 5; y++) {

    const val = grandY1 * Math.pow(1 + growthRate, y - 1);

    grandCum += val;

    html += `<td style="text-align: right; padding: 10px;">${val.toLocaleString('en-IN')}</td>`;

  }

  html += `<td style="text-align: right; padding: 10px; font-size: 1.2em; background: #c8e6c9;">${grandCum.toLocaleString('en-IN')}</td></tr>

      </tbody>

    </table>

  `;

  

  container.innerHTML = html;

}


// Update listener

window.addEventListener('totalsUpdated', updateCapexTable);


Wednesday, 28 January 2026

Estimator ( Cost Estimator)

<?php

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

// Load CSV into array


/*

Techstack Platform Env T-Shirt Size Compute License Input Notes

Oracle Cloud PROD Micro 30000 1000 License Included in Cost

Oracle Cloud COB Micro 30000 1000 License Included in Cost

*/


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 */

}


/* Dropdown styling */

select {

    padding: 8px 12px;

    border: 1px solid #ccc;

    border-radius: 6px;

    background-color: #fdfdfd;

    font-family: Arial, sans-serif;

    font-size: 14px;

    color: #333;

    transition: all 0.2s ease-in-out;

}


select:hover {

    border-color: #3498db;

    box-shadow: 0 0 5px rgba(52, 152, 219, 0.4);

}


select:focus {

    outline: none;

    border-color: #2980b9;

    box-shadow: 0 0 6px rgba(41, 128, 185, 0.5);

}


/* General Table Styling */

table {

    border-collapse: collapse;

    width: 100%;

    margin: 20px 0;

    font-family: Arial, sans-serif;

    font-size: 14px;

    background-color: #fff;

    border-radius: 8px;

    overflow: hidden;

    box-shadow: 0 2px 8px rgba(0,0,0,0.1);

}


/* Header */

table th {

    background-color: #2c3e50;

    color: #fff;

    text-transform: uppercase;

    font-weight: bold;

    padding: 12px;

    text-align: center;

}


/* Cells */

table td {

    border: 1px solid #ddd;

    padding: 10px;

    text-align: center;

    color: #333;

}


/* Zebra striping */

table tr:nth-child(even) {

    background-color: #f9f9f9;

}


/* Hover effect */

table tr:hover {

    background-color: #f1f7fd;

}


/* Grand Total row */

table .grand-total {

    background-color: #f4f6f9;

    font-weight: bold;

    border-top: 2px solid #34495e;

}


table .grand-total td {

    color: #1a5276;

    font-size: 1.05em;

}


/* Highlight one-time license Year 1 */

.one-time-license {

    background-color: #ffeaa7;

    font-weight: bold;

    color: #d35400;

}


/* CAPEX Table Header Row */

#capexTable tr:first-child {

    background: black;

    color: white;              /* white text for contrast */

    font-weight: bold;

    text-transform: uppercase;

    font-size: 15px;

    letter-spacing: 0.5px;

}


#capexTable tr:first-child td,

#capexTable tr:first-child th {

    color: white;   

    padding: 12px;

    border: none;

    text-align: center;

}


.capex-title {

    font-family: Arial, sans-serif;

    font-size: 18px;

    font-weight: bold;

    color: #2c3e50;

    margin: 10px 0;

    padding: 8px 12px;

    background-color: #ecf0f1;

    border-left: 5px solid #3498db;

    border-radius: 4px;

}




    </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>

<br>

<br>

<div class="capex-title">5-Year CAPEX Projection</div>

<table id="capexTable"></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);

    }

};


// 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();

}


// Techstacks with one-time license cost



function updateTotals() {

const oneTimeLicenseStacks = ["mongodb", "oracle", "postgresql"];

    let total = 0;

    let hwYears = [0,0,0,0,0];

    let swYears = [0,0,0,0,0];

    let hasOneTimeLicense = false;


    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);


        let techstack = row.cells[0].children[0]?.value?.toLowerCase() || "";


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


        // HW always recurring

        for (let y = 0; y < 5; y++) {

            hwYears[y] += compute * units;

        }


        // SW depends on techstack list

        if (oneTimeLicenseStacks.includes(techstack)) {

            swYears[0] += license * units;

            hasOneTimeLicense = true;

        } else {

            for (let y = 0; y < 5; y++) {

                swYears[y] += license * units;

            }

        }

    }


    grandTotal.innerHTML = `Grand Total: ${total.toFixed(0)} | HW: ${hwYears.reduce((a,b)=>a+b,0).toFixed(0)} | SW: ${swYears.reduce((a,b)=>a+b,0).toFixed(0)}`;


    generateCAPEX(hwYears, swYears, hasOneTimeLicense);

}



function generateCAPEX(hwYears, swYears, hasOneTimeLicense=false) {

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

    capexTable.innerHTML = "";


    // Header

    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";

    let hwTotal = 0;

    for (let y = 0; y < 5; y++) {

        hwRow.insertCell().innerHTML = hwYears[y].toFixed(0);

        hwTotal += hwYears[y];

    }

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


    // SW row

    let swRow = capexTable.insertRow();

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

    let swTotal = 0;

    for (let y = 0; y < 5; y++) {

        let cell = swRow.insertCell();

        cell.innerHTML = swYears[y].toFixed(0);


        // Highlight Year 1 if one-time license

        if (hasOneTimeLicense && y === 0) {

            cell.classList.add("one-time-license");

        }

        swTotal += swYears[y];

    }

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


    // Total row

    let totalRow = capexTable.insertRow();

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

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

    let grandTotal = 0;

    for (let y = 0; y < 5; y++) {

        let val = hwYears[y] + swYears[y];

        totalRow.insertCell().innerHTML = val.toFixed(0);

        grandTotal += val;

    }

    totalRow.insertCell().innerHTML = grandTotal.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");


    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>





///   PHP for Deliverables



<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $projectName = $_POST['projectName'];

    $wrNumber = $_POST['wrNumber'];

$region = $_POST['region'];

    $spLocation = rtrim($_POST['spLocation'], '/');

    $templatePath = rtrim($_POST['templatePath'], '/');

    $targetPath = rtrim($_POST['targetPath'], '/');


    // File names with format

    $bomFile = "$wrNumber-BOM-$projectName-$region-v1.0.xlsx";

    $taddFile = "$wrNumber-TADD-$projectName-$region-v1.0.xlsx";

    $nfrFile = "$wrNumber-NFR-$projectName-$region-v1.0.xlsx";

    $solutionFile = "$wrNumber-SolutionPaper-$projectName-$region-v1.0.pptx";

    $message = "";


    if (isset($_POST['copyTemplate'])) {

        // BOM

        if (file_exists("$templatePath/BOM.xlsb")) {

            copy("$templatePath/BOM.xlsb", "$targetPath/$bomFile");

            $message .= "✔ BOM copied successfully.<br>";

        } else {

            $message .= "⚠ BOM.xlsb not found.<br>";

        }


        // TADD

        if (file_exists("$templatePath/TADD.xlsx")) {

            copy("$templatePath/TADD.xlsx", "$targetPath/$taddFile");

            $message .= "✔ TADD copied successfully.<br>";

        } else {

            $message .= "⚠ TADD.xlsx not found.<br>";

        }


        // NFR

        if (file_exists("$templatePath/NFR.xlsx")) {

            copy("$templatePath/NFR.xlsx", "$targetPath/$nfrFile");

            $message .= "✔ NFR copied successfully.<br>";

        } else {

            $message .= "⚠ NFR.xlsx not found.<br>";

        }


        // Solution Paper

        if (file_exists("$templatePath/SolutionPaper.pptx")) {

            copy("$templatePath/SolutionPaper.pptx", "$targetPath/$solutionFile");

            $message .= "✔ Solution Paper copied successfully.<br>";

        } else {

            $message .= "⚠ SolutionPaper.pptx not found.<br>";

        }

    }


if (isset($_POST['pushSP'])) {

    $pushedFiles = [];


    // BOM

    if (file_exists("$targetPath/$bomFile")) {

        copy("$targetPath/$bomFile", "$spLocation/$bomFile");

        $message .= "✔ BOM pushed to SharePoint.<br>";

        $pushedFiles[] = $bomFile;

    } else {

        $message .= "⚠ BOM file not found in Target Path.<br>";

    }


    // TADD

    if (file_exists("$targetPath/$taddFile")) {

        copy("$targetPath/$taddFile", "$spLocation/$taddFile");

        $message .= "✔ TADD pushed to SharePoint.<br>";

        $pushedFiles[] = $taddFile;

    } else {

        $message .= "⚠ TADD file not found in Target Path.<br>";

    }


    // NFR

    if (file_exists("$targetPath/$nfrFile")) {

        copy("$targetPath/$nfrFile", "$spLocation/$nfrFile");

        $message .= "✔ NFR pushed to SharePoint.<br>";

        $pushedFiles[] = $nfrFile;

    } else {

        $message .= "⚠ NFR file not found in Target Path.<br>";

    }


    // Solution Paper

    if (file_exists("$targetPath/$solutionFile")) {

        copy("$targetPath/$solutionFile", "$spLocation/$solutionFile");

        $message .= "✔ Solution Paper pushed to SharePoint.<br>";

        $pushedFiles[] = $solutionFile;

    } else {

        $message .= "⚠ Solution Paper file not found in Target Path.<br>";

    }

}


}

?>



<!DOCTYPE html>

<html>

<head>

    <title>Infra Deliver - CAPEX Tool</title>

    <style>

        body {

            font-family: Arial, sans-serif;

            background: #f4f6f9;

            margin: 0;

            padding: 20px;

        }

        h1 {

            background: #2c3e50;

            color: #fff;

            padding: 15px;

            border-radius: 6px;

            text-align: center;

        }

        form {

            background: #fff;

            padding: 20px;

            border-radius: 8px;

            box-shadow: 0 2px 8px rgba(0,0,0,0.1);

            max-width: 600px;

            margin: 20px auto;

        }

        label {

            display: block;

            margin: 12px 0 6px;

            font-weight: bold;

            color: #2c3e50;

        }

        input[type="text"] {

            width: 100%;

            padding: 10px;

            border: 1px solid #ccc;

            border-radius: 6px;

            font-size: 14px;

        }

        .buttons {

            margin-top: 20px;

            display: flex;

            justify-content: space-between;

        }

        button {

            padding: 12px 20px;

            border: none;

            border-radius: 6px;

            font-size: 14px;

            cursor: pointer;

            transition: 0.3s;

        }

        button.copy {

            background: #3498db;

            color: #fff;

        }

        button.copy:hover {

            background: #2980b9;

        }

        button.push {

            background: #27ae60;

            color: #fff;

        }

        button.push:hover {

            background: #1e8449;

        }

        .message {

            margin-top: 20px;

            padding: 12px;

            background: #ecf0f1;

            border-left: 5px solid #3498db;

            border-radius: 4px;

            font-weight: bold;

            color: #2c3e50;

        }

.message {

    margin-top: 20px;

    padding: 12px;

    border-radius: 6px;

    font-weight: bold;

    font-size: 14px;

    line-height: 1.5;

}


.message.success {

    background: #eafaf1;

    border-left: 5px solid #27ae60;

    color: #1e8449;

}


.message.warning {

    background: #fff3cd;

    border-left: 5px solid #f39c12;

    color: #856404;

}


.sp-table {

    border-collapse: collapse;

    width: 100%;

    margin-top: 20px;

    font-family: Arial, sans-serif;

    font-size: 14px;

    background-color: #fff;

    border-radius: 6px;

    overflow: hidden;

    box-shadow: 0 2px 6px rgba(0,0,0,0.1);

}


.sp-table th {

    background-color: #2c3e50;

    color: #fff;

    padding: 10px;

    text-align: center;

}


.sp-table td {

    border: 1px solid #ddd;

    padding: 10px;

    text-align: center;

}


.sp-table tr:nth-child(even) {

    background-color: #f9f9f9;

}


.sp-table tr:hover {

    background-color: #f1f7fd;

}



    </style>

</head>

<body>

    <h1>Infra Delivery - Template Manager</h1>

   <form method="post">

    <label>Project Name</label>

    <input type="text" name="projectName" value="<?php echo htmlspecialchars($_POST['projectName'] ?? ''); ?>" required>


    <label>WR Number</label>

    <input type="text" name="wrNumber" value="<?php echo htmlspecialchars($_POST['wrNumber'] ?? ''); ?>" required>


<label>Region OR Country</label>

    <input type="text" name="region" value="<?php echo htmlspecialchars($_POST['region'] ?? ''); ?>" required>

    <label>SharePoint Location</label>

    <input type="text" name="spLocation" value="<?php echo htmlspecialchars($_POST['spLocation'] ?? ''); ?>" required>


    <label>Template Path</label>

    <input type="text" name="templatePath" value="<?php echo htmlspecialchars($_POST['templatePath'] ?? ''); ?>" required>


    <label>Target Local Path</label>

    <input type="text" name="targetPath" value="<?php echo htmlspecialchars($_POST['targetPath'] ?? ''); ?>" required>


    <div class="buttons">

        <button type="submit" name="copyTemplate" class="copy">Copy Template</button>

        <button type="submit" name="pushSP" class="push">Push to SP</button>

    </div>


    <?php if (!empty($message)): ?>

        <div class="message <?php echo (strpos($message, '⚠') !== false) ? 'warning' : 'success'; ?>">

            <?php echo $message; ?>

        </div>

    <?php endif; ?>

</form>


<?php if (!empty($pushedFiles)): ?>

    <h2>Pushed Files</h2>

    <table class="sp-table">

        <tr>

            <th>File</th>

            <th>SharePoint Link</th>

        </tr>

        <?php foreach ($pushedFiles as $file): 

            $fileNameNoExt = pathinfo($file, PATHINFO_FILENAME);

            $fileUrl = $spLocation . "/" . $file;

        ?>

        <tr>

            <td><?php echo $file; ?></td>

            <td><a href="<?php echo $fileUrl; ?>" target="_blank"><?php echo $fileNameNoExt; ?></a></td>

        </tr>

        <?php endforeach; ?>

    </table>

<?php endif; ?>



</body>

</html>

Saturday, 24 January 2026

TreeView

 <?php

error_reporting(E_ALL);

ini_set('display_errors', 0);

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


$mysqli = new mysqli("localhost", "xxx", "xxx", "xxxx");

if ($mysqli->connect_error) {

    echo json_encode(["error" => "DB connection failed: " . $mysqli->connect_error]);

    exit;

}


$type   = $_GET['type']   ?? '';

$value  = $_GET['value']  ?? '';   // month or other

$symbol = $_GET['symbol'] ?? '';

$symbolsParam = $_GET['symbols'] ?? '';


$result = [];


if ($type === "symbols") {


    $symbolsParam = $_GET['symbols'] ?? '';

    $sql = "SELECT DISTINCT symbol, concat(symbol,' | ','Shares') company_name, 'TECH' sector 

            FROM stock 

            WHERE symbol IN ($symbolsParam) 

            ORDER BY symbol";

    $res = $mysqli->query($sql);

    while ($row = $res->fetch_assoc()) {

        $result[] = [

            "label"    => $row['symbol'] . " (" . $row['company_name'] . ", " . $row['sector'] . ")",

            "value"    => $row['symbol'],

            "nextType" => "symbol"

        ];

    }

}

/*

elseif ($type === "symbol") {

    // Under each symbol, show 3 fixed nodes

    $result = [

        ["label" => "Database",      "value" => $symbol, "nextType" => "database",      "symbol" => $symbol],

        ["label" => "Allapps",       "value" => $symbol, "nextType" => "allapps",       "symbol" => $symbol],

        ["label" => "Microservices", "value" => $symbol, "nextType" => "microservices", "symbol" => $symbol]

    ];

}

*/

elseif ($type === "symbol") {

    $result = [

        ["label" => "Database",      "value" => $symbol, "nextType" => "database",      "symbol" => $symbol, "cssClass" => "database-label"],

        ["label" => "Allapps",       "value" => $symbol, "nextType" => "allapps",       "symbol" => $symbol, "cssClass" => "allapps-label"],

        ["label" => "Microservices", "value" => $symbol, "nextType" => "microservices", "symbol" => $symbol, "cssClass" => "microservices-label"]

    ];

}



elseif ($type === "database") {

    $stmt = $mysqli->prepare("SELECT * FROM myinv WHERE sym=?");

    $stmt->bind_param("s", $symbol);

    $stmt->execute();

    $res = $stmt->get_result();

    while ($row = $res->fetch_assoc()) {

        $result[] = $row;

    }

    $stmt->close();

}

elseif ($type === "allapps") {

    $stmt = $mysqli->prepare("SELECT * FROM corona WHERE symbol=?");

    $stmt->bind_param("s", $symbol);

    $stmt->execute();

    $res = $stmt->get_result();

    while ($row = $res->fetch_assoc()) {

        $result[] = $row;

    }

    $stmt->close();

}

elseif ($type === "microservices") {

    $stmt = $mysqli->prepare("SELECT * FROM stock WHERE symbol=?");

    $stmt->bind_param("s", $symbol);

    $stmt->execute();

    $res = $stmt->get_result();

    while ($row = $res->fetch_assoc()) {

        $result[] = $row;

    }

    $stmt->close();

}


echo json_encode($result ?? []);




#################JS ###########################



<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Stock TreeView</title>

<style>

/* General page styling */

body {

  font-family: "Segoe UI", Roboto, Arial, sans-serif;

  background: linear-gradient(135deg, #f0f4f8, #d9e2ec);

  margin: 0;

  padding: 20px;

  color: #333;

}


h2 {

  text-align: center;

  font-size: 28px;

  color: #2a4365;

  margin-bottom: 20px;

}


/* Input area */

#inputArea {

  text-align: center;

  margin-bottom: 30px;

}


textarea {

  width: 400px;

  padding: 10px;

  border: 2px solid #cbd5e0;

  border-radius: 6px;

  font-size: 14px;

  resize: none;

  transition: border-color 0.3s;

}


textarea:focus {

  border-color: #3182ce;

  outline: none;

}


button {

  padding: 10px 18px;

  margin-left: 10px;

  cursor: pointer;

  background: #3182ce;

  color: #fff;

  border: none;

  border-radius: 6px;

  font-size: 14px;

  transition: background 0.3s;

}


button:hover {

  background: #2b6cb0;

}


/* Tree styling */

ul.tree, ul.tree ul {

  list-style-type: none;

  margin: 0;

  padding: 0;

}


ul.tree li {

  margin: 6px 0;

  cursor: pointer;

  position: relative;

}


.caret::before {

  font-family: "Font Awesome

  margin-right: 6px;

}


.caret::before {

  content: "\25B6"; /* ? right-pointing triangle */

  color: #3182ce;

  font-weight: bold;

  margin-right: 6px;

  transition: transform 0.3s;

}


.caret-down::before {

  content: "\25BC"; /* ? down-pointing triangle */

}

.node-label:hover {

  background: #e2e8f0;

  transform: translateX(3px);

}


.nested {

  display: none;

  margin-left: 20px;

}


.active {

  display: block;

}


/* Shared table styling */

table {

  border-collapse: collapse;

  width: 95%;

  margin: 15px auto;

  box-shadow: 0 2px 6px rgba(0,0,0,0.1);

  border-radius: 6px;

  overflow: hidden;

}


th, td {

  border: 1px solid #e2e8f0;

  padding: 10px;

  text-align: left;

  font-size: 14px;

}


tr:nth-child(even) {

  background: #f7fafc;

}


tr:hover {

  background: #ebf8ff;

}


/* Search box styling */

input.search-box {

  margin: 10px auto;

  display: block;

  padding: 8px 12px;

  width: 50%;

  border: 2px solid #cbd5e0;

  border-radius: 6px;

  font-size: 14px;

  transition: border-color 0.3s;

}


input.search-box:focus {

  border-color: #3182ce;

  outline: none;

}


/* Section-specific themes */

.database-table th {

  background: #2b6cb0; /* Deep blue */

  color: #fff;

}


.allapps-table th {

  background: #38a169; /* Green */

  color: #fff;

}


.microservices-table th {

  background: #d69e2e; /* Amber/Orange */

  color: #fff;

}


/* Section-specific label icons */

.database-label::before {

  content: "??? "; /* database cylinder/file cabinet */

}


.allapps-label::before {

  content: "?? "; /* app icon (mobile) */

}


.microservices-label::before {

  content: "?? "; /* gear for microservices */

}


.database-label::before {

  content: "\25A3 "; /* ? square with fill */

  color: #2b6cb0;

}


.allapps-label::before {

  content: "\25A0 "; /* ¦ solid square */

  color: #38a169;

}


.microservices-label::before {

  content: "\25CF "; /* ? solid circle */

  color: #d69e2e;

}


/* Extra indentation for tables under child nodes */

.nested li div {

  margin-left: 30px;   /* pushes the wrapper (search + table) to the right */

}


.database-table {

  margin-left: 30px;

}


.allapps-table {

  margin-left: 30px;

}


.microservices-table {

  margin-left: 30px;

}


/* Indent the child headings under each symbol */

.database-label,

.allapps-label,

.microservices-label {

  margin-left: 25px;   /* push the caret + label right */

  display: inline-block;

}


ul.tree ul li .node-label {

  margin-left: 25px;

}


#symbolInputContainer {

  max-width: 500px;

  margin: 0 auto 30px auto;

  padding: 20px;

  background: #ffffff;

  border: 2px solid #cbd5e0;

  border-radius: 8px;

  box-shadow: 0 4px 10px rgba(0,0,0,0.05);

  text-align: center;

}


#symbolInputContainer h3 {

  margin-bottom: 15px;

  color: #2a4365;

  font-size: 20px;

}


/* Smaller search box */

input.search-box {

  margin: 8px auto;

  display: block;

  padding: 5px 8px;       /* reduced padding */

  width: 35%;             /* narrower width */

  border: 2px solid #cbd5e0;

  border-radius: 6px;

  font-size: 13px;        /* slightly smaller font */

  transition: border-color 0.3s;

}


input.search-box:focus {

  border-color: #3182ce;

  outline: none;

}


/* Larger font for symbols */

ul.tree > li > .node-label {

  font-size: 18px;        /* bigger font for main symbols */

  font-weight: 600;

  color: #2a4365;

}


/* Larger font for Database / Allapps / Microservices headings */

.database-label,

.allapps-label,

.microservices-label {

  font-size: 16px;        /* bigger font for child headings */

  font-weight: 500;

  color: #1a202c;

  margin-left: 25px;      /* indentation to push them right */

  display: inline-block;

}


#pageFooter {

  text-align: center;

  margin-top: 40px;

  padding: 15px;

  font-size: 14px;

  font-weight: 500;

  color: #2a4365;

  background: #edf2f7;

  border-top: 2px solid #cbd5e0;

  border-radius: 0 0 8px 8px;

}


/* Below adding */


/* 1 LINE FIX - ADD THIS LAST */

.node-label, .caret { vertical-align: top; margin-right }

.node-label { vertical-align }



/* Search + Export SAME LINE */

.table-controls-row {

  display: flex;

  align-items

}



/* Export button BELOW table, indented */

.export-btn {

  display

}

  .table-controls-row { order }


  /* Export BELOW table + indented */

.wrapper-row {

  display

}


</style>

</head>

<body>

<div id="pageWrapper">


<h2>Stock TreeView</h2>


<!-- Symbol Input -->

<div id="symbolInputContainer">

  <h3>Enter Symbols</h3>

  <textarea id="symbolsInput" rows="3" placeholder="Enter symbols (space or line separated)&#10;e.g., AAPL MSFT GOOGL"></textarea><br>

  <button onclick="buildTree()">Build Tree</button>

</div>


<!-- Tree Container -->

<div id="treeContainer"></div>


</div> <!-- pageWrapper -->


<!-- Footer -->

<footer id="pageFooter">

  Developed by Dilip | Stock TreeView © 2026

</footer>

<script>

// Global event delegation for ALL export buttons

document.addEventListener('click', function(e) {

  if (e.target.classList.contains('export-btn')) {

    const tableId = e.target.parentElement.nextElementSibling.id;

    const type = e.target.dataset.type;

    exportTableToCSV(tableId, type);

  }

});


// Export CSV - filtered rows only

function exportTableToCSV(tableId, type) {

  const table = document.getElementById(tableId);

  if (!table) return;


  let csv = [];

  

  // Header always included

  const headerRow = table.rows[0];

  let headerCols = [];

  for (let cell of headerRow.cells) {

    headerCols.push('"' + cell.innerText.replace(/"/g, '""') + '"');

  }

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


  // Only VISIBLE rows

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

    if (table.rows[i].style.display !== "none") {

      let cols = [];

      for (let cell of table.rows[i].cells) {

        cols.push('"' + cell.innerText.replace(/"/g, '""') + '"');

      }

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

    }

  }


  const now = new Date();

  const dateStr = now.toLocaleDateString('en-GB', { 

    day: '2-digit', month: 'short', year: 'numeric' 

  }).replace(/ /g, '-');

  const filename = `${type}_${dateStr}.csv`;


  const blob = new Blob([csv.join("\n")], { type: "text/csv" });

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

  link.href = URL.createObjectURL(blob);

  link.download = filename;

  link.click();

}


function buildTree() {

  const input = document.getElementById("symbolsInput").value.trim();

  const symbols = input.split(/\s+/).filter(s => s.trim());

  

  if (symbols.length === 0) {

    alert("Please enter symbols");

    return;

  }


  const formatted = symbols.map(s => `'${s.toUpperCase()}'`).join(",");

  const container = document.getElementById("treeContainer");

  container.innerHTML = '<p style="text-align:center;">Loading tree...</p>';


  fetch(`backend.php?type=symbols&symbols=${encodeURIComponent(formatted)}`)

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

    .then(data => {

      container.innerHTML = "";

      if (!Array.isArray(data) || data.length === 0) {

        container.innerHTML = '<p style="color:red;text-align:center;">No symbols found</p>';

        return;

      }


      const ul = document.createElement("ul");

      ul.className = "tree";


      data.forEach(item => {

        const li = document.createElement("li");

        const span = document.createElement("span");

        span.className = "caret node-label";

        span.textContent = item.label;

        span.onclick = () => toggleNode(span, item.nextType, item.value);

        li.appendChild(span);


        const nested = document.createElement("ul");

        nested.className = "nested";

        li.appendChild(nested);

        ul.appendChild(li);

      });


      container.appendChild(ul);

    })

    .catch(err => {

      document.getElementById("treeContainer").innerHTML = 

        `<p style="color:red;text-align:center;">Network error: ${err}</p>`;

    });

}


function toggleNode(element, type, value, symbol = "") {

  element.classList.toggle("caret-down");

  const nested = element.nextElementSibling;


  if (!nested.classList.contains("active")) {

    const url = `backend.php?type=${type}&value=${value}&symbol=${symbol || value}`;

    

    fetch(url)

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

      .then(data => {

        nested.innerHTML = "";

        

        if (!Array.isArray(data)) {

          nested.innerHTML = `<li style="color:red;">Error: ${data.error || "Invalid data"}</li>`;

          nested.classList.add("active");

          return;

        }


        if (type === "database" || type === "allapps" || type === "microservices") {

          // **TABLE DATA - FIXED VERSION**

          if (data.length > 0) {

            const wrapper = document.createElement("div");


            // Controls: Search + Export (NO onclick here!)

            const controlsRow = document.createElement("div");

            controlsRow.className = "table-controls-row";


            const searchBox = document.createElement("input");

            searchBox.type = "text";

            searchBox.placeholder = "Search records...";

            searchBox.className = "search-box";


            const exportBtn = document.createElement("button");

            exportBtn.textContent = "Export CSV";

            exportBtn.className = "export-btn";

            exportBtn.dataset.type = type;  // Store type for delegation


            controlsRow.appendChild(searchBox);

            controlsRow.appendChild(exportBtn);

            wrapper.appendChild(controlsRow);


            // Table with ID

            const tableId = `${type}-table-${symbol || value}`;

            const table = document.createElement("table");

            table.id = tableId;


            if (type === "database") table.classList.add("database-table");

            if (type === "allapps") table.classList.add("allapps-table");

            if (type === "microservices") table.classList.add("microservices-table");


            // Build table

            const headerRow = document.createElement("tr");

            Object.keys(data[0]).forEach(key => {

              const th = document.createElement("th");

              th.textContent = key;

              headerRow.appendChild(th);

            });

            table.appendChild(headerRow);


            data.forEach(row => {

              const tr = document.createElement("tr");

              Object.values(row).forEach(val => {

                const td = document.createElement("td");

                td.textContent = val || '';

                tr.appendChild(td);

              });

              table.appendChild(tr);

            });


            // **Search works** - no export handler needed here

            searchBox.addEventListener("keyup", function() {

              const filter = this.value.toLowerCase();

              const rows = table.getElementsByTagName("tr");

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

                const rowText = rows[i].textContent.toLowerCase();

                rows[i].style.display = rowText.includes(filter) ? "" : "none";

              }

            });


            wrapper.appendChild(table);


            const li = document.createElement("li");

            li.appendChild(wrapper);

            nested.appendChild(li);

          } else {

            nested.innerHTML = "<li>No records found</li>";

          }

        } else {

          // Tree nodes

          data.forEach(item => {

            const li = document.createElement("li");

            const span = document.createElement("span");

            span.className = `caret node-label ${item.cssClass || ""}`;

            span.textContent = item.label;

            span.onclick = () => toggleNode(span, item.nextType, item.value, item.symbol || symbol);

            li.appendChild(span);

            const childUl = document.createElement("ul");

            childUl.className = "nested";

            li.appendChild(childUl);

            nested.appendChild(li);

          });

        }

        

        nested.classList.add("active");

      })

      .catch(err => {

        nested.innerHTML = `<li style="color:red;">Fetch error: ${err}</li>`;

        nested.classList.add("active");

      });

  } else {

    nested.classList.toggle("active");

  }

}


function updateCapexTable() {

  const year1Capex = window.grandTotalForCapex || 0;

  const growthRate = 0.08; // Edit as needed

  

  let html = `

    <table id="capexTable" border="1" style="border-collapse: collapse; margin-top: 20px; font-family: monospace;">

      <thead style="background: #e3f2fd; font-weight: bold;">

        <tr>

          <th style="padding: 12px 8px;">Particulars</th>

          <th style="padding: 12px 8px;">Grand Total<br>(Year 1 Base)</th>

          <th style="padding: 12px 8px;">Year 2<br>(+8%)</th>

          <th style="padding: 12px 8px;">Year 3</th>

          <th style="padding: 12px 8px;">Year 4</th>

          <th style="padding: 12px 8px;">Year 5</th>

          <th style="padding: 12px 8px;">Cumulative<br>Total</th>

        </tr>

      </thead>

      <tbody>

        <tr style="background: #f8f9ff; font-weight: bold;">

          <td style="padding: 10px 8px;">Capex (₹ Cr)</td>

          <td style="text-align: right; padding: 10px 8px;">${year1Capex.toLocaleString('en-IN')}</td>`;


  let cumulative = year1Capex;

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

    const capex = year1Capex * Math.pow(1 + growthRate, year - 1);

    cumulative += capex;

    html += `<td style="text-align: right; padding: 10px 8px;">${capex.toLocaleString('en-IN')}</td>`;

  }

  

  html += `

          <td style="text-align: right; padding: 10px 8px; background: #c8e6c9; font-size: 1.1em;">${cumulative.toLocaleString('en-IN')}</td>

        </tr>

      </tbody>

    </table>

    <div style="margin-top: 10px; font-size: 0.9em; color: #666;">

      Growth Rate: ${growthRate*100}% | Base from your table: ₹${year1Capex.toLocaleString('en-IN')} Cr

    </div>

  `;

  

  document.getElementById('capexContainer').innerHTML = html;

}

</script>




</body>

</html>