UI improvements
This commit is contained in:
parent
7b5c827635
commit
e908f53e8b
@ -184,12 +184,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group" style="grid-column: 4;">
|
<div class="filter-group" style="grid-column: 4;">
|
||||||
<label for="dateFrom">Od:</label>
|
<label for="selectedDate">Den:</label>
|
||||||
<input type="datetime-local" id="dateFrom">
|
<input type="date" id="selectedDate">
|
||||||
</div>
|
|
||||||
<div class="filter-group" style="grid-column: 5;">
|
|
||||||
<label for="dateTo">Do:</label>
|
|
||||||
<input type="datetime-local" id="dateTo">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-buttons">
|
<div class="filter-buttons">
|
||||||
<button onclick="applyFilters()">Použít filtry</button>
|
<button onclick="applyFilters()">Použít filtry</button>
|
||||||
@ -220,28 +216,16 @@
|
|||||||
|
|
||||||
<div class="charts-grid">
|
<div class="charts-grid">
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<h3>Stav procesů</h3>
|
<h3>Doba procesů vs. čas bez nalezených procesů</h3>
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<canvas id="statusChart"></canvas>
|
<canvas id="processTimeChart"></canvas>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chart-container">
|
|
||||||
<h3>Stavy podle strojů</h3>
|
|
||||||
<div class="chart-wrapper">
|
|
||||||
<canvas id="machineChart"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chart-container" style="grid-column: 1 / -1;">
|
|
||||||
<h3>Dostupnost v čase</h3>
|
|
||||||
<div class="chart-wrapper" style="height: 400px;">
|
|
||||||
<canvas id="timelineChart"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let statusChart, machineChart, timelineChart;
|
let processTimeChart;
|
||||||
const apiKey = '%API_KEY%';
|
const apiKey = '%API_KEY%';
|
||||||
|
|
||||||
function normalizeStatus(status) {
|
function normalizeStatus(status) {
|
||||||
@ -284,9 +268,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
document.getElementById('selectedDate').value = now.toISOString().slice(0, 10);
|
||||||
document.getElementById('dateTo').value = now.toISOString().slice(0, 16);
|
|
||||||
document.getElementById('dateFrom').value = sevenDaysAgo.toISOString().slice(0, 16);
|
|
||||||
|
|
||||||
applyFilters();
|
applyFilters();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -300,8 +282,7 @@
|
|||||||
const machine = document.getElementById('machine').value;
|
const machine = document.getElementById('machine').value;
|
||||||
const process = document.getElementById('process').value;
|
const process = document.getElementById('process').value;
|
||||||
const status = document.getElementById('status').value;
|
const status = document.getElementById('status').value;
|
||||||
const dateFrom = document.getElementById('dateFrom').value;
|
const selectedDate = document.getElementById('selectedDate').value;
|
||||||
const dateTo = document.getElementById('dateTo').value;
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('type', 'stats');
|
params.append('type', 'stats');
|
||||||
@ -309,8 +290,12 @@
|
|||||||
if (machine) params.append('machine', machine);
|
if (machine) params.append('machine', machine);
|
||||||
if (process) params.append('process', process);
|
if (process) params.append('process', process);
|
||||||
if (status) params.append('status', status);
|
if (status) params.append('status', status);
|
||||||
if (dateFrom) params.append('from', new Date(dateFrom).toISOString());
|
if (selectedDate) {
|
||||||
if (dateTo) params.append('to', new Date(dateTo).toISOString());
|
const from = selectedDate + 'T00:00:00Z';
|
||||||
|
const to = selectedDate + 'T23:59:59Z';
|
||||||
|
params.append('from', from);
|
||||||
|
params.append('to', to);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await axios.get('/hb/api/data?' + params.toString());
|
const response = await axios.get('/hb/api/data?' + params.toString());
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
@ -337,21 +322,62 @@
|
|||||||
function updateCharts(data) {
|
function updateCharts(data) {
|
||||||
const records = data.records;
|
const records = data.records;
|
||||||
|
|
||||||
const statusCounts = {};
|
// Agregace času podle procesů
|
||||||
records.forEach(r => {
|
const processTimeMap = {};
|
||||||
const normalized = normalizeStatus(r.status);
|
|
||||||
statusCounts[normalized] = (statusCounts[normalized] || 0) + 1;
|
// Seřadit záznamy podle procesu a času
|
||||||
|
const sortedRecords = records.sort((a, b) =>
|
||||||
|
new Date(a.detected_at) - new Date(b.detected_at)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seskupit podle procesu a spočítat časy
|
||||||
|
const processByName = {};
|
||||||
|
sortedRecords.forEach(r => {
|
||||||
|
const processName = r.process_name || '(bez procesu)';
|
||||||
|
if (!processByName[processName]) {
|
||||||
|
processByName[processName] = [];
|
||||||
|
}
|
||||||
|
processByName[processName].push(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (statusChart) statusChart.destroy();
|
// Spočítat čas běhu pro každý proces
|
||||||
const statusCtx = document.getElementById('statusChart').getContext('2d');
|
Object.keys(processByName).forEach(processName => {
|
||||||
statusChart = new Chart(statusCtx, {
|
const records = processByName[processName];
|
||||||
|
let totalTimeMs = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < records.length - 1; i++) {
|
||||||
|
const current = new Date(records[i].detected_at);
|
||||||
|
const next = new Date(records[i + 1].detected_at);
|
||||||
|
totalTimeMs += (next - current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Převést na minuty
|
||||||
|
const totalMinutes = Math.round(totalTimeMs / 60000);
|
||||||
|
processTimeMap[processName] = totalMinutes;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (processTimeChart) processTimeChart.destroy();
|
||||||
|
const processTimeCtx = document.getElementById('processTimeChart').getContext('2d');
|
||||||
|
|
||||||
|
// Příprava labels s časy
|
||||||
|
const labels = Object.keys(processTimeMap).map(processName => {
|
||||||
|
const minutes = processTimeMap[processName];
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const mins = minutes % 60;
|
||||||
|
const timeStr = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
|
||||||
|
return `${processName} (${timeStr})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
processTimeChart = new Chart(processTimeCtx, {
|
||||||
type: 'doughnut',
|
type: 'doughnut',
|
||||||
data: {
|
data: {
|
||||||
labels: Object.keys(statusCounts),
|
labels: labels,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: Object.values(statusCounts),
|
data: Object.values(processTimeMap),
|
||||||
backgroundColor: ['#4CAF50', '#FF6B6B'],
|
backgroundColor: [
|
||||||
|
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
|
||||||
|
'#F7DC6F', '#BB8FCE', '#85C1E2', '#F8B88B', '#ABEBC6'
|
||||||
|
],
|
||||||
borderColor: '#fff',
|
borderColor: '#fff',
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
}]
|
}]
|
||||||
@ -360,90 +386,21 @@
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { position: 'bottom' }
|
legend: { position: 'bottom' },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const minutes = context.parsed;
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const mins = minutes % 60;
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h ${mins}m`;
|
||||||
|
} else {
|
||||||
|
return `${mins}m`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const statusByMachine = {};
|
|
||||||
records.forEach(r => {
|
|
||||||
const normalized = normalizeStatus(r.status);
|
|
||||||
if (!statusByMachine[r.machine_name]) {
|
|
||||||
statusByMachine[r.machine_name] = { UP: 0, DOWN: 0 };
|
|
||||||
}
|
|
||||||
if (normalized === 'UP') {
|
|
||||||
statusByMachine[r.machine_name].UP++;
|
|
||||||
} else if (normalized === 'DOWN') {
|
|
||||||
statusByMachine[r.machine_name].DOWN++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (machineChart) machineChart.destroy();
|
|
||||||
const machineCtx = document.getElementById('machineChart').getContext('2d');
|
|
||||||
machineChart = new Chart(machineCtx, {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: Object.keys(statusByMachine),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'UP',
|
|
||||||
backgroundColor: '#4CAF50',
|
|
||||||
data: Object.values(statusByMachine).map(s => s.UP)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'DOWN',
|
|
||||||
backgroundColor: '#FF6B6B',
|
|
||||||
data: Object.values(statusByMachine).map(s => s.DOWN)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
scales: {
|
|
||||||
x: { stacked: true },
|
|
||||||
y: { stacked: true }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const sortedRecords = records.sort((a, b) =>
|
|
||||||
new Date(a.detected_at) - new Date(b.detected_at)
|
|
||||||
);
|
|
||||||
|
|
||||||
const timeLabels = sortedRecords.map(r =>
|
|
||||||
new Date(r.detected_at).toLocaleString('cs-CZ')
|
|
||||||
);
|
|
||||||
const upCounts = [];
|
|
||||||
let upCount = 0;
|
|
||||||
sortedRecords.forEach(r => {
|
|
||||||
if (normalizeStatus(r.status) === 'UP') upCount++;
|
|
||||||
upCounts.push(upCount);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (timelineChart) timelineChart.destroy();
|
|
||||||
const timelineCtx = document.getElementById('timelineChart').getContext('2d');
|
|
||||||
timelineChart = new Chart(timelineCtx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: timeLabels,
|
|
||||||
datasets: [{
|
|
||||||
label: 'Procesy UP',
|
|
||||||
borderColor: '#4CAF50',
|
|
||||||
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
|
||||||
data: upCounts,
|
|
||||||
fill: true,
|
|
||||||
tension: 0.4
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: { position: 'bottom' }
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: { beginAtZero: true }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -453,6 +410,8 @@
|
|||||||
document.getElementById('machine').value = '';
|
document.getElementById('machine').value = '';
|
||||||
document.getElementById('process').value = '';
|
document.getElementById('process').value = '';
|
||||||
document.getElementById('status').value = '';
|
document.getElementById('status').value = '';
|
||||||
|
const now = new Date();
|
||||||
|
document.getElementById('selectedDate').value = now.toISOString().slice(0, 10);
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,6 +429,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', loadFilters);
|
window.addEventListener('load', loadFilters);
|
||||||
|
document.getElementById('selectedDate')?.addEventListener('change', applyFilters);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user