updated interface
This commit is contained in:
@@ -1,26 +1,24 @@
|
|||||||
class DashboardController < ApplicationController
|
class DashboardController < ApplicationController
|
||||||
def index
|
def index
|
||||||
joint_column = 'joint'
|
joint_column = 'joint'
|
||||||
# Grab distinct values for dropdown filters
|
|
||||||
@available_joints = Joint.distinct.pluck(joint_column)
|
@available_joints = Joint.distinct.pluck(joint_column)
|
||||||
|
@available_data
|
||||||
# Fetch initial records
|
|
||||||
@records = filter_records
|
@records = filter_records
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html # Renders app/views/dashboards/index.html.erb
|
format.html
|
||||||
format.json { render json: @records } # Returns filtered data as JSON for JS
|
format.json { render json: @records }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def filter_records
|
def filter_records
|
||||||
if params.include?(:joint)
|
# Using .where(joint: params[:joint]) handles both present and nil values gracefully
|
||||||
records = Joint.all
|
if params[:joint].present?
|
||||||
records = records.where(joint: params[:joint])
|
Joint.where(joint: params[:joint])
|
||||||
records
|
|
||||||
else
|
else
|
||||||
|
# Return empty array if no joint selected to avoid loading thousands of records
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,88 +1,164 @@
|
|||||||
<h1>Excel Replacement Dashboard</h1>
|
<h1 style="font-family: Arial, sans-serif;">Excel Replacement Dashboard</h1>
|
||||||
|
|
||||||
<!-- Dropdown Filters (No submit button needed, JS handles it) -->
|
<!-- Dropdown Filters -->
|
||||||
<%= form_with url: dashboard_path, method: :get, id: "filter-form" do |f| %>
|
<%= form_with url: dashboard_path, method: :get, id: "filter-form" do |f| %>
|
||||||
<div>
|
<div style="margin-bottom: 20px; font-family: Arial, sans-serif;">
|
||||||
<%= f.label :joint, "Select Joint:" %>
|
<%= f.label :joint, "Select Joint:", style: "font-weight: bold;" %>
|
||||||
<%= f.select :joint, options_for_select(@available_joints, params[:joint]), include_blank: "Select a joint", id: "joint" %>
|
<%= f.select :joint, options_for_select(@available_joints, params[:joint]), include_blank: "Select a joint", id: "joint" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>Dashboard Results</h2>
|
<h2 style="font-family: Arial, sans-serif;">Dashboard Results</h2>
|
||||||
<div style="overflow-x: auto;">
|
|
||||||
<table border="1" id="results-table">
|
<!-- Container where the formatted "Excel-style" grids will be injected -->
|
||||||
<thead>
|
<div id="results-container" style="display: flex; flex-wrap: wrap; gap: 30px;">
|
||||||
<tr id="table-headers">
|
<!-- JS will inject the tables here -->
|
||||||
<!-- Dynamically populated headers -->
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
<!-- Dynamically populated rows (supports 4 to 20+ values) -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Main Table Layout */
|
||||||
|
.joint-grid {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 2px solid #000;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
.joint-grid td, .joint-grid th {
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 4px 8px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color Palette from Image */
|
||||||
|
.bg-dark-blue { background-color: #002060; color: white; font-weight: bold; }
|
||||||
|
.bg-light-blue { background-color: #D9E1F2; font-weight: bold; }
|
||||||
|
.bg-pale-yellow { background-color: #FFFFCC; }
|
||||||
|
.bg-pale-green { background-color: #E2EFDA; }
|
||||||
|
.bg-pale-orange { background-color: #FCE4D6; }
|
||||||
|
|
||||||
|
.text-red { color: #C00000; text-align: left !important; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const jointSelect = document.getElementById("joint");
|
const jointSelect = document.getElementById("joint");
|
||||||
const tableHeaders = document.getElementById("table-headers");
|
const resultsContainer = document.getElementById("results-container");
|
||||||
const tableBody = document.getElementById("table-body");
|
|
||||||
|
|
||||||
function fetchFilteredData() {
|
function fetchFilteredData() {
|
||||||
const joint = jointSelect.value;
|
const joint = jointSelect.value;
|
||||||
|
|
||||||
// Build query parameters
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (joint) params.append("joint", joint);
|
if (joint) params.append("joint", joint);
|
||||||
|
|
||||||
// Fetch data from Rails with JSON format
|
|
||||||
fetch(`/dashboard?${params.toString()}`, {
|
fetch(`/dashboard?${params.toString()}`, {
|
||||||
headers: { "Accept": "application/json" }
|
headers: { "Accept": "application/json" }
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
renderTable(data);
|
renderJointGrid(data);
|
||||||
})
|
})
|
||||||
.catch(error => console.error("Error fetching dashboard data:", error));
|
.catch(error => console.error("Error fetching dashboard data:", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTable(records) {
|
/**
|
||||||
tableHeaders.innerHTML = "";
|
* This function transforms a flat JSON record into the
|
||||||
tableBody.innerHTML = "";
|
* specific grid layout seen in the image.
|
||||||
|
*/
|
||||||
|
function renderJointGrid(records) {
|
||||||
|
resultsContainer.innerHTML = "";
|
||||||
|
|
||||||
if (records.length === 0) {
|
if (records.length === 0) {
|
||||||
tableBody.innerHTML = "<tr><td colspan='100'>No records found</td></tr>";
|
resultsContainer.innerHTML = "<p>No records found. Please select a joint.</p>";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract column keys dynamically from the first record (handles variable lengths)
|
|
||||||
const columns = Object.keys(records[0]);
|
|
||||||
|
|
||||||
// 1. Build dynamic headers
|
|
||||||
columns.forEach(col => {
|
|
||||||
const th = document.createElement("th");
|
|
||||||
th.textContent = col.replace(/_/g, " ").toUpperCase();
|
|
||||||
tableHeaders.appendChild(th);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Build dynamic rows (whether 4 or 20 attributes long)
|
|
||||||
records.forEach(record => {
|
records.forEach(record => {
|
||||||
const tr = document.createElement("tr");
|
// Create a wrapper for the two tables (Info and Ligaments)
|
||||||
columns.forEach(col => {
|
const wrapper = document.createElement("div");
|
||||||
const td = document.createElement("td");
|
wrapper.style.display = "flex";
|
||||||
td.textContent = record[col] !== null ? record[col] : "";
|
wrapper.style.gap = "20px";
|
||||||
tr.appendChild(td);
|
|
||||||
});
|
// --- TABLE 1: Joint Information ---
|
||||||
tableBody.appendChild(tr);
|
const infoTable = document.createElement("table");
|
||||||
|
infoTable.className = "joint-grid";
|
||||||
|
|
||||||
|
// Use a template literal to define the exact structure of the image
|
||||||
|
// Note: We map JSON keys (record["Joint"]) to specific cells
|
||||||
|
infoTable.innerHTML = `
|
||||||
|
<thead>
|
||||||
|
<tr><th colspan="4" class="bg-dark-blue">Joint Information</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="bg-light-blue">Anatomical Name</td>
|
||||||
|
<td class="bg-pale-yellow">${record["Joint"] || ""}</td>
|
||||||
|
<td class="bg-light-blue">Common Name</td>
|
||||||
|
<td class="bg-pale-yellow">${record["Common Name"] || ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="bg-light-blue">Diarthrodial Joint Type</td>
|
||||||
|
<td class="bg-pale-yellow">${record["Diarthrodial joint"] || ""}</td>
|
||||||
|
<td class="bg-light-blue">Planes of Freedom</td>
|
||||||
|
<td class="bg-pale-yellow">${record["# of Plains"] || ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="bg-pale-green">${record["Alternate Name"] || ""}</td>
|
||||||
|
<td class="bg-pale-green">Ball & Socket</td>
|
||||||
|
<td class="bg-pale-green">${record["# of Plains"] || ""}</td>
|
||||||
|
<td class="bg-pale-green">${record["# of Plains"] || ""}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="bg-light-blue">Arthrokinematic Rule</td>
|
||||||
|
<td class="bg-pale-orange">Osteokinematic</td>
|
||||||
|
<td class="bg-light-blue">Open Chain Roll/Glide</td>
|
||||||
|
<td class="bg-pale-orange">Anterior/Posterior</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="bg-light-blue">Open Pack</td>
|
||||||
|
<td colspan="2" class="bg-pale-green">${record["Open Pack"] || "N/A"}</td>
|
||||||
|
<td class="bg-light-blue">Close Pack</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="bg-pale-green">${record["Close Pack"] || "N/A"}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// --- TABLE 2: Ligaments ---
|
||||||
|
const ligTable = document.createElement("table");
|
||||||
|
ligTable.className = "joint-grid";
|
||||||
|
ligTable.style.width = "250px";
|
||||||
|
|
||||||
|
// Handle ligaments (assuming they might be a comma-separated string or array)
|
||||||
|
let ligsHtml = "";
|
||||||
|
if (record["Ligaments"]) {
|
||||||
|
const ligArray = Array.isArray(record["Ligaments"]) ? record["Ligaments"] : record["Ligaments"].split(",");
|
||||||
|
ligsHtml = ligArray.map(l => `<div class="text-red">${l.trim()}</div>`).join("");
|
||||||
|
} else {
|
||||||
|
ligsHtml = "No ligaments listed";
|
||||||
|
}
|
||||||
|
|
||||||
|
ligTable.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td class="bg-dark-blue" style="width: 100px;">Ligaments</td>
|
||||||
|
<td class="bg-pale-orange" style="text-align: left;">
|
||||||
|
<strong>Vertebral Articulating Processes</strong><br>
|
||||||
|
${ligsHtml}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
|
wrapper.appendChild(infoTable);
|
||||||
|
wrapper.appendChild(ligTable);
|
||||||
|
resultsContainer.appendChild(wrapper);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger fetch when either dropdown changes
|
|
||||||
jointSelect.addEventListener("change", fetchFilteredData);
|
jointSelect.addEventListener("change", fetchFilteredData);
|
||||||
|
|
||||||
// Load initial data on page load
|
|
||||||
fetchFilteredData();
|
fetchFilteredData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user