<?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) 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");
}
}
</script>
</body>
</html>
No comments:
Post a Comment