Files
Comparison_Project/app/templates/dashboard.html
2026-02-02 12:27:27 +05:30

376 lines
16 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="container-fluid py-4" style="background-color: #f8f9fa;">
<h3 class="mb-4 fw-bold text-uppercase">Abstract Excavation Dashboard</h3>
<div class="card shadow-sm mb-4">
<div class="card-body bg-white">
<div class="row g-3">
<div class="col-md-2">
<label class="form-label fw-bold">Comparison Type</label>
<select id="filter-table" class="form-select" onchange="loadDashboardData()">
<option value="trench">Trench Excavation</option>
<option value="manhole">Manhole Excavation</option>
<option value="laying">Laying</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold">Subcontractor</label>
<select id="filter-subcon" class="form-select" onchange="loadDashboardData()">
<option value="All">All Subcontractors</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold">RA Bill No</label>
<select id="filter-ra" class="form-select" onchange="loadDashboardData()">
<option value="Cumulative">Cumulative (All Bills)</option>
</select>
</div>
<div class="col-md-4 d-flex align-items-end gap-2">
<button class="btn btn-primary flex-grow-1" onclick="loadDashboardData()">🔄 Refresh</button>
<button class="btn btn-secondary flex-grow-1" onclick="clearDashboard()">🗑️ Clear</button>
</div>
</div>
</div>
</div>
<!-- Empty State (Shown on page load) -->
<div id="empty-state" class="alert alert-info text-center py-5">
<h5>📊 Select filters to display data</h5>
<p>Choose a Subcontractor and/or RA Bill to see the excavation abstract comparison.</p>
</div>
<!-- Data Display Area (Hidden by default) -->
<div id="data-area" style="display: none;">
<div class="row">
<div class="col-lg-8">
<div class="card shadow-sm h-100">
<div class="card-header bg-primary text-white fw-bold d-flex justify-content-between align-items-center">
<span id="chart-title">Excavation Comparison: Client vs Subcontractor Qty</span>
<small class=\"fw-normal\">(Horizontal Bar Chart)</small>
</div>
<div class=\"card-body\" style=\"position: relative; height: 700px; overflow-y: auto;\">
<canvas id="groupedBarChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white fw-bold">Excavation Abstract Table</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 500px; overflow-y: auto;">
<table class="table table-hover mb-0" id="abstract-table">
<thead class="table-light sticky-top">
<tr>
<th class="small">Soil / Depth</th>
<th class="small">Client (m³)</th>
<th class="small">Subcon (m³)</th>
<th class="small">Diff</th>
</tr>
</thead>
<tbody></tbody>
<tfoot class="table-light fw-bold position-sticky bottom-0">
<tr id="table-totals" class="bg-light"></tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
let comparisonChart;
// Define color palette - ONLY 2 COLORS
const colorPalette = {
'client': '#003D7A', // Dark Blue for Client (RA Bill)
'subcon': '#87CEEB' // Light Sky Blue for Subcontractor
};
// 1. Function to Initialize or Update the Chart (VERTICAL BARS - 2 COLORS ONLY)
function updateChartUI(labels, clientData, subconData) {
const ctx = document.getElementById('groupedBarChart').getContext('2d');
if (comparisonChart) comparisonChart.destroy();
comparisonChart = new Chart(ctx, {
type: 'bar', // Vertical bar chart
data: {
labels: labels,
datasets: [
{
label: 'Client Qty (m³)',
data: clientData,
backgroundColor: colorPalette.client,
borderColor: '#001F4D',
borderWidth: 1,
borderRadius: 4,
hoverBackgroundColor: '#002A5C',
hoverBorderWidth: 2
},
{
label: 'Subcontractor Qty (m³)',
data: subconData,
backgroundColor: colorPalette.subcon,
borderColor: '#4A90B8',
borderWidth: 1,
borderRadius: 4,
hoverBackgroundColor: '#6BB3D9',
hoverBorderWidth: 2
}
]
},
options: {
indexAxis: 'x', // Vertical bars (default)
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
position: 'top',
labels: {
font: { size: 14, weight: 'bold' },
padding: 15,
usePointStyle: true,
boxWidth: 15
}
},
tooltip: {
backgroundColor: '#2c3e50',
padding: 12,
titleFont: { size: 13, weight: 'bold' },
bodyFont: { size: 12 },
borderColor: '#fff',
borderWidth: 1,
displayColors: true,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) label += ': ';
label += Number(context.parsed.y).toLocaleString('en-IN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' m³';
return label;
}
}
}
},
scales: {
x: {
stacked: false,
grid: {
display: false
},
ticks: {
font: { size: 11 },
maxRotation: 45,
minRotation: 0
}
},
y: {
stacked: false,
beginAtZero: true,
grid: {
color: '#ecf0f1',
drawBorder: false
},
ticks: {
font: { size: 11 },
callback: function(value) {
return Number(value).toLocaleString('en-IN');
}
},
title: {
display: true,
text: 'Excavation Quantity (m³)',
font: { size: 12, weight: 'bold' }
}
}
}
}
});
}
// 2. Function to fetch unique filters (Subcontractors & RA Bills) from DB
function loadFilters() {
console.log("🔄 Loading filters from /dashboard/api/filters...");
fetch('/dashboard/api/filters')
.then(res => {
console.log(`Response status: ${res.status}`);
return res.json();
})
.then(data => {
console.log("✓ Filter data received:", data);
const raSelect = document.getElementById('filter-ra');
// CRITICAL: This clears the "RA-01", "RA-02" you typed in manually
raSelect.innerHTML = '<option value="Cumulative">Cumulative (All Bills)</option>';
if (data.ra_bills && data.ra_bills.length > 0) {
console.log(`Adding ${data.ra_bills.length} RA bills to dropdown`);
data.ra_bills.forEach(billNo => {
let opt = document.createElement('option');
opt.value = billNo;
opt.innerText = billNo; // This will show exactly what's in the DB
raSelect.appendChild(opt);
console.log(` + Added RA Bill: ${billNo}`);
});
} else {
console.warn("❌ No RA bills found in response");
}
// Repeat same for subcontractor dropdown
const subconSelect = document.getElementById('filter-subcon');
subconSelect.innerHTML = '<option value="All">All Subcontractors</option>';
if (data.subcontractors && data.subcontractors.length > 0) {
data.subcontractors.forEach(name => {
let opt = document.createElement('option');
opt.value = name;
opt.innerText = name;
subconSelect.appendChild(opt);
});
}
console.log("✓ Filters loaded successfully");
})
.catch(err => {
console.error("❌ Error loading filters:", err);
});
}
// 3. Main function to load data and reflect in UI
function loadDashboardData() {
const tableType = document.getElementById('filter-table').value;
const subcon = document.getElementById('filter-subcon').value;
const ra = document.getElementById('filter-ra').value;
console.log(`📊 Filter values: Table="${tableType}", Subcon="${subcon}", RA="${ra}"`);
// If still on default values, don't load
if (subcon === 'All' && ra === 'Cumulative') {
console.warn("⚠️ Please select filters first");
return;
}
// Update chart title
const tableNames = {
'trench': 'Trench Excavation',
'manhole': 'Manhole Excavation',
'laying': 'Laying'
};
const chartTitle = document.getElementById('chart-title');
if (chartTitle) {
chartTitle.textContent = `${tableNames[tableType]}: Client (RA Bill) vs Subcontractor Qty`;
}
console.log(`📊 Loading dashboard data: Table="${tableType}", Subcon="${subcon}", RA="${ra}"`);
const url = `/dashboard/api/excavation-abstract?table_type=${encodeURIComponent(tableType)}&subcontractor=${encodeURIComponent(subcon)}&ra_bill=${encodeURIComponent(ra)}`;
console.log(`Fetching from URL: ${url}`);
fetch(url)
.then(res => {
console.log(`Response status: ${res.status}`);
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status}`);
}
return res.json();
})
.then(data => {
console.log("✓ Dashboard data received:", data);
if (!Array.isArray(data)) {
console.error("❌ Response is not an array:", data);
return;
}
if (data.length === 0) {
console.warn("⚠️ No data returned for this filter combination");
alert("No data found for selected filters");
return;
}
const labels = [];
const clientData = [];
const subconData = [];
const tableBody = document.querySelector("#abstract-table tbody");
tableBody.innerHTML = "";
let tClient = 0, tSub = 0, tDiff = 0;
data.forEach(item => {
// Label format: "Soil Type Depth"
const label = `${item.soil_type}\n${item.depth}`;
labels.push(label);
clientData.push(item.client_qty || 0);
subconData.push(item.subcon_qty || 0);
tClient += item.client_qty || 0;
tSub += item.subcon_qty || 0;
tDiff += (item.difference || 0);
const diffColor = (item.difference || 0) < 0 ? 'text-danger' : 'text-success';
tableBody.innerHTML += `
<tr>
<td class="small">
<strong>${item.soil_type}</strong>
<br>
<span class="text-muted small">${item.depth}</span>
</td>
<td class="small text-primary fw-bold">${(item.client_qty || 0).toLocaleString('en-IN', {maximumFractionDigits: 2})}</td>
<td class="small text-success fw-bold">${(item.subcon_qty || 0).toLocaleString('en-IN', {maximumFractionDigits: 2})}</td>
<td class="small fw-bold ${diffColor}">${(item.difference || 0).toLocaleString('en-IN', {maximumFractionDigits: 2})}</td>
</tr>
`;
});
const totalDiffColor = tDiff < 0 ? 'text-danger' : 'text-success';
document.getElementById('table-totals').innerHTML = `
<td class="small fw-bold">TOTAL</td>
<td class="small fw-bold text-primary">${tClient.toLocaleString('en-IN', {maximumFractionDigits: 2})}</td>
<td class="small fw-bold text-success">${tSub.toLocaleString('en-IN', {maximumFractionDigits: 2})}</td>
<td class="small fw-bold ${totalDiffColor}">${tDiff.toLocaleString('en-IN', {maximumFractionDigits: 2})}</td>
`;
// Show data area
document.getElementById('empty-state').style.display = 'none';
document.getElementById('data-area').style.display = 'block';
updateChartUI(labels, clientData, subconData);
console.log("✓ Chart and table updated successfully");
})
.catch(err => {
console.error("❌ Error loading dashboard data:", err);
alert(`Failed to load dashboard data: ${err.message}`);
});
}
// Clear dashboard
function clearDashboard() {
console.log("🗑️ Clearing dashboard...");
document.getElementById('filter-table').value = 'trench';
document.getElementById('filter-subcon').value = 'All';
document.getElementById('filter-ra').value = 'Cumulative';
document.getElementById('empty-state').style.display = 'block';
document.getElementById('data-area').style.display = 'none';
if (comparisonChart) comparisonChart.destroy();
}
// Start: Load filters only, don't auto-load data
document.addEventListener("DOMContentLoaded", () => {
console.log("🚀 Dashboard initialized");
loadFilters();
// Don't auto-load data - keep dashboard blank until filters selected
});
</script>
{% endblock %}