vxzfc / index.html
Akarimvand's picture
Add 3 files
e61a5d1 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Commissioning Management Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #f8fafc;
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.animate-fadeIn {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.progress-bar {
height: 6px;
border-radius: 3px;
background-color: #e2e8f0;
}
.progress-bar-fill {
height: 100%;
border-radius: 3px;
transition: width 0.6s ease;
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-800">Commissioning Management Dashboard</h1>
<p class="text-gray-600">Real-time tracking of form status, item progress, and punch list clearance</p>
</header>
<!-- Section 1: Summary Cards -->
<section class="mb-12 animate-fadeIn">
<h2 class="text-xl font-semibold mb-6 text-gray-700">Key Performance Indicators</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6" id="kpiCards">
<!-- Cards will be dynamically inserted here -->
<div class="bg-white rounded-xl shadow-sm p-6 card-hover flex items-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span class="ml-4 text-gray-500">Loading KPIs...</span>
</div>
</div>
</section>
<!-- Section 2: Item Progress by Discipline -->
<section class="mb-12 animate-fadeIn">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-700">Item Progress by Discipline</h2>
<div class="flex space-x-2">
<button class="px-3 py-1 bg-blue-50 text-blue-600 rounded-md text-sm font-medium">Export</button>
<button class="px-3 py-1 bg-blue-50 text-blue-600 rounded-md text-sm font-medium">Filter</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden mb-8">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" id="itemProgressTable">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Discipline</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Items</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Done</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">In Progress</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Hold</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Remain</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Progress</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="itemProgressBody">
<!-- Data will be dynamically inserted here -->
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
<span class="ml-2">Loading item progress data...</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6">
<canvas id="itemProgressChart"></canvas>
</div>
</section>
<!-- Section 3: Punch List Status by Discipline -->
<section class="animate-fadeIn">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-700">Punch List Status by Discipline</h2>
<div class="flex space-x-2">
<button class="px-3 py-1 bg-blue-50 text-blue-600 rounded-md text-sm font-medium">Export</button>
<button class="px-3 py-1 bg-blue-50 text-blue-600 rounded-md text-sm font-medium">Filter</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm overflow-hidden mb-8">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" id="punchStatusTable">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Discipline</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Punch</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cleared</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">In Progress</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ready For Approve</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Remain</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Progress</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="punchStatusBody">
<!-- Data will be dynamically inserted here -->
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
<span class="ml-2">Loading punch status data...</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6">
<canvas id="punchStatusChart"></canvas>
</div>
</section>
</div>
<script>
// Global variables to store chart instances
let itemProgressChart;
let punchStatusChart;
// Sample data (as fallback)
const sampleDisciplineData = `Total Subsystems: 45
Form A: 12
Form B: 8
Form C: 15
Form D: 10
Mechanical|120|80|20|10|10
Electrical|95|60|15|10|10
Civil|80|50|15|5|10
Instrumentation|65|40|10|5|10
Piping|110|70|20|10|10`;
const samplePunchData = `Mechanical|45|20|10|5|10
Electrical|35|15|8|5|7
Civil|25|10|5|3|7
Instrumentation|20|8|4|3|5
Piping|40|18|10|5|7`;
// Fetch data with CORS proxy
async function fetchWithProxy(url) {
try {
const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
const response = await fetch(proxyUrl + url);
return await response.text();
} catch (error) {
console.error('Error using proxy:', error);
// Return sample data if proxy fails
if (url.includes('discipline')) return sampleDisciplineData;
if (url.includes('punch')) return samplePunchData;
return '';
}
}
// Fetch data from GitHub
async function fetchData() {
try {
// Fetch discipline data
const disciplineText = await fetchWithProxy('https://raw.githubusercontent.com/akarimvand/hos/refs/heads/main/discipline.txt');
const disciplineData = parseDisciplineData(disciplineText);
// Fetch punch data
const punchText = await fetchWithProxy('https://raw.githubusercontent.com/akarimvand/hos/refs/heads/main/punch.txt');
const punchData = parsePunchData(punchText);
// Update the UI with the fetched data
updateKPICards(disciplineData.kpi);
updateItemProgressTable(disciplineData.items);
updatePunchStatusTable(punchData);
createItemProgressChart(disciplineData.items);
createPunchStatusChart(punchData);
} catch (error) {
console.error('Error fetching data:', error);
showError('Failed to load data. Using sample data instead.');
// Use sample data as fallback
const disciplineData = parseDisciplineData(sampleDisciplineData);
const punchData = parsePunchData(samplePunchData);
updateKPICards(disciplineData.kpi);
updateItemProgressTable(disciplineData.items);
updatePunchStatusTable(punchData);
createItemProgressChart(disciplineData.items);
createPunchStatusChart(punchData);
}
}
// Parse discipline data from text
function parseDisciplineData(text) {
const lines = text.split('\n');
const data = {
kpi: {},
items: []
};
// Parse KPI data (first few lines)
for (let i = 0; i < 5; i++) {
if (lines[i]) {
const [key, value] = lines[i].split(':').map(item => item.trim());
data.kpi[key] = parseInt(value);
}
}
// Parse item progress data (remaining lines)
for (let i = 5; i < lines.length; i++) {
if (lines[i].trim()) {
const parts = lines[i].split('|').map(item => item.trim());
if (parts.length >= 6) {
data.items.push({
discipline: parts[0],
total: parseInt(parts[1]),
done: parseInt(parts[2]),
inProgress: parseInt(parts[3]),
hold: parseInt(parts[4]),
remain: parseInt(parts[5])
});
}
}
}
return data;
}
// Parse punch data from text
function parsePunchData(text) {
const lines = text.split('\n');
const data = [];
for (let i = 0; i < lines.length; i++) {
if (lines[i].trim()) {
const parts = lines[i].split('|').map(item => item.trim());
if (parts.length >= 6) {
data.push({
discipline: parts[0],
total: parseInt(parts[1]),
cleared: parseInt(parts[2]),
inProgress: parseInt(parts[3]),
readyForApprove: parseInt(parts[4]),
remain: parseInt(parts[5])
});
}
}
}
return data;
}
// Update KPI cards
function updateKPICards(kpiData) {
const kpiCards = document.getElementById('kpiCards');
const cards = [
{
title: 'Total Subsystems',
value: kpiData['Total Subsystems'] || 0,
icon: 'fas fa-layer-group',
color: 'bg-blue-100',
textColor: 'text-blue-600'
},
{
title: 'Completed Form A',
value: kpiData['Form A'] || 0,
icon: 'fas fa-file-alt',
color: 'bg-green-100',
textColor: 'text-green-600'
},
{
title: 'Completed Form B',
value: kpiData['Form B'] || 0,
icon: 'fas fa-file-invoice',
color: 'bg-orange-100',
textColor: 'text-orange-600'
},
{
title: 'Completed Form C',
value: kpiData['Form C'] || 0,
icon: 'fas fa-file-signature',
color: 'bg-purple-100',
textColor: 'text-purple-600'
}
];
kpiCards.innerHTML = cards.map(card => `
<div class="bg-white rounded-xl shadow-sm p-6 card-hover">
<div class="flex items-center">
<div class="p-3 rounded-lg ${card.color} ${card.textColor} mr-4">
<i class="${card.icon} text-xl"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-500">${card.title}</p>
<h3 class="text-2xl font-bold text-gray-800">${card.value}</h3>
</div>
</div>
</div>
`).join('');
}
// Update item progress table
function updateItemProgressTable(items) {
const tableBody = document.getElementById('itemProgressBody');
tableBody.innerHTML = items.map(item => {
const progress = ((item.done / item.total) * 100).toFixed(1);
return `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.discipline}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.total}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.done}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.inProgress}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.hold}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.remain}</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="w-16 mr-2">
<div class="progress-bar">
<div class="progress-bar-fill bg-blue-500" style="width: ${progress}%"></div>
</div>
</div>
<span class="text-xs text-gray-500">${progress}%</span>
</div>
</td>
</tr>
`;
}).join('');
}
// Update punch status table
function updatePunchStatusTable(punchData) {
const tableBody = document.getElementById('punchStatusBody');
tableBody.innerHTML = punchData.map(item => {
const progress = ((item.cleared / item.total) * 100).toFixed(1);
return `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.discipline}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.total}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.cleared}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.inProgress}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.readyForApprove}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.remain}</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="w-16 mr-2">
<div class="progress-bar">
<div class="progress-bar-fill bg-green-500" style="width: ${progress}%"></div>
</div>
</div>
<span class="text-xs text-gray-500">${progress}%</span>
</div>
</td>
</tr>
`;
}).join('');
}
// Create item progress chart
function createItemProgressChart(items) {
const ctx = document.getElementById('itemProgressChart').getContext('2d');
// Destroy previous chart if it exists
if (itemProgressChart) {
itemProgressChart.destroy();
}
const labels = items.map(item => item.discipline);
const doneData = items.map(item => item.done);
const inProgressData = items.map(item => item.inProgress);
const holdData = items.map(item => item.hold);
const remainData = items.map(item => item.remain);
itemProgressChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Done',
data: doneData,
backgroundColor: '#10B981',
stack: 'Stack 0'
},
{
label: 'In Progress',
data: inProgressData,
backgroundColor: '#3B82F6',
stack: 'Stack 0'
},
{
label: 'Hold',
data: holdData,
backgroundColor: '#F59E0B',
stack: 'Stack 0'
},
{
label: 'Remain',
data: remainData,
backgroundColor: '#EF4444',
stack: 'Stack 0'
}
]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Item Progress Breakdown by Discipline',
font: {
size: 16
}
},
legend: {
position: 'bottom',
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
beginAtZero: true,
ticks: {
precision: 0
}
}
},
animation: {
duration: 1000
}
}
});
}
// Create punch status chart
function createPunchStatusChart(punchData) {
const ctx = document.getElementById('punchStatusChart').getContext('2d');
// Destroy previous chart if it exists
if (punchStatusChart) {
punchStatusChart.destroy();
}
const labels = punchData.map(item => item.discipline);
const clearedData = punchData.map(item => item.cleared);
const inProgressData = punchData.map(item => item.inProgress);
const readyData = punchData.map(item => item.readyForApprove);
const remainData = punchData.map(item => item.remain);
punchStatusChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Cleared',
data: clearedData,
backgroundColor: '#10B981',
stack: 'Stack 0'
},
{
label: 'In Progress',
data: inProgressData,
backgroundColor: '#3B82F6',
stack: 'Stack 0'
},
{
label: 'Ready For Approve',
data: readyData,
backgroundColor: '#8B5CF6',
stack: 'Stack 0'
},
{
label: 'Remain',
data: remainData,
backgroundColor: '#EF4444',
stack: 'Stack 0'
}
]
},
options: {
indexAxis: 'y',
responsive: true,
plugins: {
title: {
display: true,
text: 'Punch List Status by Discipline',
font: {
size: 16
}
},
legend: {
position: 'bottom',
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
beginAtZero: true,
grid: {
display: false
}
}
},
animation: {
duration: 1000
}
}
});
}
// Show error message
function showError(message) {
const kpiCards = document.getElementById('kpiCards');
kpiCards.innerHTML = `
<div class="col-span-4 bg-red-50 border-l-4 border-red-500 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-red-700">${message}</p>
</div>
</div>
</div>
`;
}
// Initialize the dashboard when the page loads
document.addEventListener('DOMContentLoaded', fetchData);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Akarimvand/vxzfc" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>