Files
oee/templates/dashboard.html
2026-07-18 16:04:40 +00:00

1343 lines
47 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CRI — MIS Dashboard</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
<style>
/* ============ TOKENS ============ */
:root{
--bg:#0a1018;
--panel:#121b28;
--panel-2:#182333;
--border:#263547;
--steel:#3d4d61;
--text:#e7ecf2;
--text-dim:#8a97a8;
--text-faint:#5b6b80;
--bg:#0a1018;
--panel:#121b28;
--panel-2:#182333;
--border:#263547;
--steel:#3d4d61;
--text:#e7ecf2;
--text-dim:#8a97a8;
--text-faint:#5b6b80;
--amber:#10b981;
--amber-dim:#3a2f1c;
--green:#35c97a;
--green-dim:#173226;
--red:#e8604a;
--red-dim:#3a1e1a;
--blue:#4fa3e3;
--blue-dim:#152736;
--display:'Rajdhani',sans-serif;
--body:'Inter',sans-serif;
--mono:'JetBrains Mono',monospace;
--radius:10px;
--sidebar-w:252px;
}
body.light-theme{
--bg:#f5f7fa;
--panel:#ffffff;
--panel-2:#f1f5f9;
--border:#dbe3ec;
--steel:#94a3b8;
--text:#1e293b;
--text-dim:#475569;
--text-faint:#64748b;
--amber:#f59e0b;
--amber-dim:#fef3c7;
--green:#16a34a;
--green-dim:#dcfce7;
--red:#dc2626;
--red-dim:#fee2e2;
--blue:#2563eb;
--blue-dim:#dbeafe;
}
.theme-btn{
width:40px;
height:40px;
border:none;
border-radius:8px;
cursor:pointer;
background:var(--panel);
color:var(--text);
border:1px solid var(--border);
transition:.3s;
}
.theme-btn:hover{
background:var(--panel-2);
}
*{box-sizing:border-box;}
body{
margin:0;
padding:0;
background:var(--bg);
color:var(--text);
font-family:var(--body);
min-height:100vh;
}
::selection{background:var(--amber);color:#0a1018;}
/*Maintaenance Sub tabs*/
/* ============ SUBTABS (used inside Maintenance) ============ */
.subtabs{
display:flex;gap:8px;margin-bottom:20px;
border-bottom:1px solid var(--border);
}
.subtab-btn{
font-family:var(--mono);font-size:12px;letter-spacing:.3px;
background:none;border:none;color:var(--text-faint);
padding:10px 16px;cursor:pointer;position:relative;
display:flex;align-items:center;gap:8px;
border-bottom:2px solid transparent;margin-bottom:-1px;
transition:color .18s ease;
}
.subtab-btn i{font-size:12px;}
.subtab-btn:hover{color:var(--text-dim);}
.subtab-btn.active{color:var(--amber);border-bottom-color:var(--amber);}
.subtab-page{display:none;animation:fadein .3s ease;}
.subtab-page.active{display:block;}
/* Filter */
.filter-panel{
display:flex;
flex-wrap:wrap;
gap:15px;
background:#fff;
padding:20px;
border-radius:10px;
margin-bottom:20px;
box-shadow:0 2px 8px rgba(0,0,0,.08);
}
.filter-group{
display:flex;
flex-direction:column;
min-width:200px;
}
.filter-group label{
font-size:13px;
font-weight:600;
margin-bottom:6px;
}
.filter-group input,
.filter-group select{
height:42px;
border:1px solid #ccc;
border-radius:6px;
padding:0 12px;
}
.btn-primary{
margin-top:24px;
height:42px;
padding:0 25px;
border:none;
border-radius:6px;
background:#0d6efd;
color:#fff;
cursor:pointer;
}
/* new break down */
.modal{
display:none;
position:fixed;
left:0;
top:0;
width:100%;
height:100%;
background:rgba(0,0,0,.45);
z-index:9999;
}
.modal-content{
width:850px;
max-width:95%;
background:#fff;
border-radius:10px;
margin:50px auto;
overflow:hidden;
}
.modal-header{
display:flex;
justify-content:space-between;
align-items:center;
padding:18px 25px;
border-bottom:1px solid #eee;
}
.modal-body{
padding:25px;
}
.close{
cursor:pointer;
font-size:28px;
}
.form-grid{
display:grid;
grid-template-columns:repeat(2,1fr);
gap:20px;
}
.form-grid input,
.form-grid select,
textarea{
width:100%;
padding:10px;
border:1px solid #ccc;
border-radius:6px;
}
.btn-secondary{
padding:10px 18px;
border:none;
background:#999;
color:#fff;
border-radius:6px;
cursor:pointer;
}
/* ============ SIDEBAR ============ */
.sidebar{
position:fixed;
left:0;
top:0;
width:var(--sidebar-w);
height:100vh;
background:var(--panel);
border-right:1px solid var(--border);
display:flex;
flex-direction:column;
z-index:10;
}
.logo{
padding:26px 25px 22px;
border-bottom:1px solid var(--border);
position:relative;
}
.logo::after{
content:"";position:absolute;left:0;bottom:-1px;width:64px;height:1px;
background:var(--amber);
}
.logo .mark{
display:flex;align-items:center;gap:10px;
font-family:var(--display);font-weight:700;font-size:23px;letter-spacing:0.5px;
color:var(--text);
}
.logo .mark i{
color:var(--amber);font-size:19px;
}
.logo small{
display:block;color:var(--text-faint);font-size:10.5px;margin-top:6px;
letter-spacing:2.5px;font-weight:600;text-transform:uppercase;
font-family:var(--mono);
}
.menu{margin-top:14px;flex:1;}
.menu a{
display:flex;align-items:center;gap:14px;
color:var(--text-dim);text-decoration:none;
padding:14px 25px;font-size:14.5px;font-weight:500;
border-left:3px solid transparent;
cursor:pointer;position:relative;
transition:background .18s ease, color .18s ease;
}
.menu a i{width:18px;text-align:center;font-size:15px;color:var(--text-faint);transition:color .18s ease;}
.menu a:hover{background:rgba(255,255,255,.03);color:var(--text);}
.menu a:hover i{color:var(--text-dim);}
.menu a.active{
background:linear-gradient(90deg, rgba(242,169,59,.09), transparent);
color:var(--text);border-left:3px solid var(--amber);
}
.menu a.active i{color:var(--amber);}
.sidebar-foot{
padding:18px 25px 22px;border-top:1px solid var(--border);
font-family:var(--mono);font-size:11px;color:var(--text-faint);
display:flex;justify-content:space-between;
}
.sidebar-foot .dot{color:var(--green);}
/* ============ MAIN ============ */
.main{margin-left:var(--sidebar-w);}
.header{
height:72px;background:rgba(18,27,40,.7);backdrop-filter:blur(10px);
display:flex;justify-content:space-between;align-items:center;
padding:0 32px;border-bottom:1px solid var(--border);
position:sticky;top:0;z-index:5;
}
.header .title-block h2{
font-family:var(--display);font-weight:600;font-size:20px;color:var(--text);letter-spacing:.3px;
}
.header .title-block span{
font-size:11.5px;color:var(--text-faint);font-family:var(--mono);letter-spacing:1px;
}
.header-right{display:flex;align-items:center;gap:22px;}
.shift-badge{
font-family:var(--mono);font-size:11.5px;background:var(--panel-2);
border:1px solid var(--border);color:var(--amber);
padding:6px 12px;border-radius:20px;letter-spacing:.5px;
}
.clock{font-family:var(--mono);font-size:13px;color:var(--text-dim);}
.user{
display:flex;align-items:center;gap:10px;font-weight:600;font-size:14px;
padding-left:20px;border-left:1px solid var(--border);
}
.user .avatar{
width:34px;height:34px;border-radius:50%;
background:linear-gradient(135deg,var(--amber),#c97f1f);
display:flex;align-items:center;justify-content:center;
color:#0a1018;font-family:var(--display);font-weight:700;
}
.content{padding:30px 32px 60px;max-width:1400px;}
.page{display:none;animation:fadein .35s ease;}
.page.active{display:block;}
@keyframes fadein{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
.section-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:20px;}
.section-head h2{font-family:var(--display);font-weight:600;font-size:24px;letter-spacing:.3px;}
.section-head p{color:var(--text-faint);font-size:13px;margin-top:4px;}
.eyebrow{
font-family:var(--mono);font-size:11px;color:var(--amber);letter-spacing:2px;
text-transform:uppercase;margin-bottom:6px;
}
/* ============ GAUGE CLUSTER (signature element) ============ */
.gauge-row{
display:grid;grid-template-columns:repeat(4,1fr);gap:20px;margin-bottom:24px;
}
.gauge-card{
background:linear-gradient(180deg,var(--panel),var(--panel-2));
border:1px solid var(--border);border-radius:var(--radius);
padding:20px 18px 16px;
display:flex;flex-direction:column;align-items:center;
position:relative;overflow:hidden;
}
.gauge-card::before{
content:"";position:absolute;top:0;left:0;right:0;height:2px;
background:linear-gradient(90deg,transparent,var(--gauge-color,var(--amber)),transparent);
}
.gauge-card .g-label{
font-family:var(--mono);font-size:11px;letter-spacing:1.5px;text-transform:uppercase;
color:var(--text-faint);margin-bottom:6px;
}
.gauge-card svg{width:150px;height:96px;}
.gauge-card .g-value{
font-family:var(--display);font-weight:700;font-size:30px;margin-top:-6px;
color:var(--text);
}
.gauge-card .g-value span{font-size:16px;color:var(--text-faint);font-weight:600;}
.gauge-card .g-sub{font-size:11.5px;color:var(--text-faint);margin-top:2px;}
/* ============ CARDS ============ */
.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;margin-bottom:24px;}
.card{
background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
padding:22px 22px;position:relative;
}
.card h4{
font-family:var(--mono);color:var(--text-faint);font-size:11px;
letter-spacing:1.5px;text-transform:uppercase;font-weight:500;
}
.card h1{
font-family:var(--display);font-weight:700;color:var(--text);margin-top:12px;font-size:32px;
}
.card .trend{font-size:12px;margin-top:8px;font-family:var(--mono);}
.trend.up{color:var(--green);}
.trend.down{color:var(--red);}
/* ============ PANEL / CHART ============ */
.panel{
background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
padding:22px 24px;margin-top:22px;
}
.panel-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;}
.panel-head h3{font-family:var(--display);font-weight:600;font-size:16.5px;letter-spacing:.3px;}
.panel-head .tag{
font-family:var(--mono);font-size:10.5px;color:var(--text-faint);
border:1px solid var(--border);padding:3px 9px;border-radius:20px;
}
.chart-box{height:280px;position:relative;}
/* ============ MACHINE STATUS STRIP ============ */
.machine-strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px;margin-top:22px;}
.mchip{
background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
padding:14px 16px;display:flex;flex-direction:column;gap:8px;
}
.mchip .mtop{display:flex;justify-content:space-between;align-items:center;}
.mchip .mname{font-family:var(--display);font-weight:600;font-size:14.5px;}
.mchip .pulse{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 0 rgba(53,201,122,.6);animation:pulse 1.8s infinite;}
.mchip.idle .pulse{background:var(--text-faint);animation:none;box-shadow:none;}
.mchip .mval{font-family:var(--mono);font-size:20px;font-weight:700;}
.mchip .mmeta{font-size:11px;color:var(--text-faint);font-family:var(--mono);}
@keyframes pulse{
0%{box-shadow:0 0 0 0 rgba(53,201,122,.55);}
70%{box-shadow:0 0 0 7px rgba(53,201,122,0);}
100%{box-shadow:0 0 0 0 rgba(53,201,122,0);}
}
/* ============ TABLE ============ */
table{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-top:18px;}
table thead th{
background:var(--panel-2);color:var(--text-dim);text-align:left;
padding:13px 18px;font-family:var(--mono);font-size:11px;letter-spacing:1px;
text-transform:uppercase;font-weight:500;border-bottom:1px solid var(--border);
}
table td{padding:14px 18px;border-bottom:1px solid var(--border);font-size:13.5px;color:var(--text);}
table tr:last-child td{border-bottom:none;}
table tbody tr{transition:background .15s ease;}
table tbody tr:hover{background:rgba(255,255,255,.02);}
.status-pill{
display:inline-flex;align-items:center;gap:6px;
font-size:11.5px;font-weight:600;padding:5px 11px;border-radius:20px;
font-family:var(--mono);letter-spacing:.3px;
}
.status-pill.running{background:var(--green-dim);color:var(--green);}
.status-pill.idle{background:var(--amber-dim);color:var(--amber);}
.status-pill.down{background:var(--red-dim);color:var(--red);}
.status-pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;}
/* placeholder empty state */
.empty-state{
display:flex;flex-direction:column;align-items:center;justify-content:center;
padding:70px 20px;color:var(--text-faint);
}
.empty-state i{font-size:34px;margin-bottom:14px;color:var(--steel);}
.empty-state h3{font-family:var(--display);color:var(--text-dim);font-size:17px;margin-bottom:6px;}
.empty-state p{font-size:13px;max-width:340px;text-align:center;}
@media(max-width:1100px){
.gauge-row,.cards{grid-template-columns:repeat(2,1fr);}
}
</style>
</head>
<body>
<!-- ============ SIDEBAR ============ -->
<div class="sidebar">
<div class="logo">
<div class="mark"><i class="fa-solid fa-gauge-high"></i> CRI PUMPS</div>
<small>Digital Manufacturing</small>
</div>
<div class="menu">
<a class="active" onclick="showPage('dashboard',this)"><i class="fa-solid fa-chart-line"></i>Dashboard</a>
<a onclick="showPage('production',this)"><i class="fa-solid fa-industry"></i>Production</a>
<a onclick="showPage('oee',this)"><i class="fa-solid fa-gauge-high"></i>OEE</a>
<a onclick="showPage('quality',this)"><i class="fa-solid fa-circle-check"></i>Quality</a>
<a onclick="showPage('maintenance',this)"><i class="fa-solid fa-wrench"></i>Maintenance</a>
<a onclick="showPage('reports',this)"><i class="fa-solid fa-file-lines"></i>Reports</a>
<a onclick="showPage('users',this)"><i class="fa-solid fa-users"></i>Users</a>
<a href="/"><i class="fa-solid fa-right-from-bracket"></i>Logout</a>
</div>
<div class="sidebar-foot">
<span><i class="fa-solid fa-circle dot" style="font-size:7px;"></i> System Online</span>
<span id="footclock">--:--:--</span>
</div>
</div>
<!-- ============ MAIN ============ -->
<div class="main">
<div class="header">
<div class="title-block">
<h2>Digital Manufacturing — MIS</h2>
<span id="pageTag">DASHBOARD OVERVIEW</span>
</div>
<div class="header-right">
<button id="themeToggle" class="theme-btn">
<i class="fa-solid fa-moon"></i>
</button>
<div class="shift-badge"><i class="fa-solid fa-clock"></i> Shift A · 06:0014:00</div>
<div class="clock" id="headerClock">--:--:--</div>
<div class="user">
<div class="avatar">A</div>
Admin
</div>
</div>
</div>
<div class="content">
<!-- ============ DASHBOARD ============ -->
<div id="dashboard" class="page active">
<div class="section-head">
<div>
<div class="eyebrow">Live Overview</div>
<h2>Plant Performance</h2>
</div>
<div class="tag" style="font-family:var(--mono);font-size:11px;color:var(--text-faint);border:1px solid var(--border);padding:5px 12px;border-radius:20px;">Updated <span id="lastUpdate">just now</span></div>
</div>
<div class="gauge-row" id="gaugeRow"></div>
<div class="panel">
<div class="panel-head">
<h3>OEE Trend — Last 7 Days</h3>
<div class="tag">% daily average</div>
</div>
<div class="chart-box"><canvas id="oeeTrendChart"></canvas></div>
</div>
<div class="section-head" style="margin-top:30px;">
<div>
<div class="eyebrow">Floor Status</div>
<h2>Machine Line</h2>
</div>
</div>
<div class="machine-strip" id="machineStrip"></div>
</div>
<!-- ============ PRODUCTION ============ -->
<div id="production" class="page">
<div class="section-head">
<div>
<div class="eyebrow">Production</div>
<h2>Production Details</h2>
</div>
</div>
<div class="cards">
<div class="card">
<h4>Total Plants</h4>
<h1>{{ total_plants }}</h1>
<div class="trend up"><i class="fa-solid fa-arrow-up"></i> Active sites</div>
</div>
<div class="card">
<h4>Total Serial Invoices</h4>
<h1>{{ total_serial }}</h1>
<div class="trend up"><i class="fa-solid fa-arrow-up"></i> Processed today</div>
</div>
<div class="card">
<h4>Completed Serial Invoices</h4>
<h1>{{ scanned_serial }}</h1>
<div class="trend up"><i class="fa-solid fa-arrow-up"></i> Completed today</div>
</div>
<div class="card">
<h4>Units Produced</h4>
<h1>2,650</h1>
<div class="trend up"><i class="fa-solid fa-arrow-up"></i> 4.2% vs yesterday</div>
</div>
<div class="card">
<h4>Downtime</h4>
<h1>18<span style="font-size:16px;color:var(--text-faint);"> min</span></h1>
<div class="trend down"><i class="fa-solid fa-arrow-down"></i> Within target</div>
</div>
</div>
<table>
<thead>
<tr><th>Machine</th><th>Shift</th><th>Production</th><th>Status</th></tr>
</thead>
<tbody>
<tr><td>Machine 01</td><td>A</td><td>1,450 units</td><td><span class="status-pill running">Running</span></td></tr>
<tr><td>Machine 02</td><td>A</td><td>1,200 units</td><td><span class="status-pill running">Running</span></td></tr>
<tr><td>Machine 03</td><td>A</td><td>0 units</td><td><span class="status-pill down">Down</span></td></tr>
<tr><td>Machine 04</td><td>A</td><td>860 units</td><td><span class="status-pill idle">Idle</span></td></tr>
</tbody>
</table>
</div>
<!-- ============ OEE ============ -->
<div id="oee" class="page">
<div class="section-head">
<div>
<div class="eyebrow">Efficiency</div>
<h2>OEE Dashboard</h2>
</div>
</div>
<div class="gauge-row" id="gaugeRowOee"></div>
<div class="panel">
<div class="panel-head"><h3>Availability · Performance · Quality — 7 Day</h3><div class="tag">%</div></div>
<div class="chart-box"><canvas id="apqChart"></canvas></div>
</div>
</div>
<!-- ============ QUALITY ============ -->
<div id="quality" class="page">
<div class="section-head">
<div>
<div class="eyebrow">Quality</div>
<h2>Quality Dashboard</h2>
</div>
</div>
<div class="cards">
<div class="card"><h4>First Pass Yield</h4><h1>98.4%</h1><div class="trend up"><i class="fa-solid fa-arrow-up"></i> 0.6% vs last week</div></div>
<div class="card"><h4>Rejected Units</h4><h1>36</h1><div class="trend down"><i class="fa-solid fa-arrow-down"></i> 12 fewer than yesterday</div></div>
<div class="card"><h4>Rework</h4><h1>14</h1><div class="trend down"><i class="fa-solid fa-arrow-down"></i> Stable</div></div>
<div class="card"><h4>Customer Returns</h4><h1>2</h1><div class="trend down"><i class="fa-solid fa-arrow-down"></i> Low</div></div>
</div>
<div class="panel">
<div class="panel-head"><h3>Defect Rate — Last 7 Days</h3><div class="tag">ppm</div></div>
<div class="chart-box"><canvas id="qualityChart"></canvas></div>
</div>
</div>
<div id="maintenance" class="page">
<div class="section-head">
<div>
<div class="eyebrow">Asset Management</div>
<h2>Maintenance</h2>
</div>
</div>
<div class="subtabs">
<button class="subtab-btn active"
onclick="showSubtab('breakdownLog',this)">
<i class="fa-solid fa-screwdriver-wrench"></i>
Breakdown Log
</button>
<button class="subtab-btn"
onclick="showSubtab('spareParts',this)">
<i class="fa-solid fa-boxes-stacked"></i>
Spare Parts
</button>
<button class="subtab-btn"
onclick="showSubtab('machineHealth',this)">
<i class="fa-solid fa-heart-pulse"></i>
Machine Health
</button>
<button class="subtab-btn"
onclick="showSubtab('maintenanceHistory',this)">
<i class="fa-solid fa-clock-rotate-left"></i>
Maintenance History
</button>
</div>
<!-- Sub-tab: PM Calendar -->
<div id="breakdownLog" class="subtab-page active">
<div style="display:flex;justify-content:flex-end;margin:20px 0;">
<button class="btn-primary" onclick="openBreakdownModal()">
<i class="fa-solid fa-plus"></i>
New Breakdown
</button>
</div>
<div class="cards">
<div class="card"><h4>Scheduled This Week</h4><h1>4</h1><div class="trend" style="color:var(--text-faint);">Across all lines</div></div>
<div class="card"><h4>Overdue</h4><h1 style="color:var(--red);">1</h1><div class="trend down"><i class="fa-solid fa-arrow-down"></i> Needs attention</div></div>
<div class="card"><h4>Completed This Month</h4><h1>11</h1><div class="trend up"><i class="fa-solid fa-arrow-up"></i> On schedule</div></div>
<div class="card"><h4>Avg. Completion Time</h4><h1>1h 40<span style="font-size:16px;color:var(--text-faint);">m</span></h1><div class="trend" style="color:var(--text-faint);">Per service</div></div>
</div>
<!-- Breakdown Modal -->
<div id="breakdownModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>New Breakdown</h3>
<span class="close" onclick="closeBreakdownModal()">
&times;
</span>
</div>
<div class="modal-body">
<form id="breakdownForm">
<div class="form-grid">
<div>
<label>Plant</label>
<select id="plant_id" name="plant_id">
<option value="">Select Plant</option>
{% for plant in plants %}
<option value="{{ plant.id }}">
{{ plant.name }}
</option>
{% endfor %}
</select>
</div>
<div>
<label>Line</label>
<select id="line_id" name="line_id">
<option value="">Select Line</option>
{% for line in lines %}
<option value="{{ line.id }}">
{{ line.name }}
</option>
{% endfor %}
</select>
</div>
<div>
<label>Machine</label>
<input
type="text"
id="machine_name"
name="machine_name"
placeholder="Machine Name">
</div>
<div>
<label>Breakdown Date</label>
<input
type="date"
id="breakdown_date"
name="breakdown_date">
</div>
<div>
<label>Breakdown Time</label>
<input
type="time"
id="breakdown_time"
name="breakdown_time">
</div>
<div>
<label>Down Time</label>
<input
type="time"
id="down_time"
name="down_time">
</div>
</div>
<div style="margin-top:20px;">
<label>Reason</label>
<textarea
id="reason"
name="reason"
rows="4"
placeholder="Enter breakdown reason"></textarea>
</div>
<div style="text-align:right;margin-top:20px;">
<button
type="button"
class="btn-secondary"
onclick="closeBreakdownModal()">
Cancel
</button>
<button
type="button"
class="btn-primary"
onclick="saveBreakdown()">
Save Breakdown
</button>
</div>
</form>
</div>
</div>
</div>
<table>
<thead>
<tr>
<th>Plant</th>
<th>Line</th>
<th>Machine</th>
<th>Breakdown Date</th>
<th>Breakdown Time</th>
<th>Downtime</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
{% for row in breakdowns %}
<tr>
<td>{{ row.plant.name }}</td>
<td>{{ row.line.name }}</td>
<td>{{ row.machine_name }}</td>
<td>{{ row.breakdown_at|date:"d M Y" }}</td>
<td>{{ row.breakdown_time }}</td>
<td>{{ row.down_time }}</td>
<td>{{ row.reason }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7" style="text-align:center;">
No breakdowns found
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Sub-tab: Spare Parts -->
<div id="spareParts" class="subtab-page">
<div class="cards">
<div class="card"><h4>Total SKUs Tracked</h4><h1>28</h1><div class="trend" style="color:var(--text-faint);">Critical spares</div></div>
<div class="card"><h4>Below Reorder Level</h4><h1 style="color:var(--amber);">3</h1><div class="trend down"><i class="fa-solid fa-arrow-down"></i> Reorder soon</div></div>
<div class="card"><h4>Out of Stock</h4><h1 style="color:var(--red);">1</h1><div class="trend down"><i class="fa-solid fa-arrow-down"></i> Urgent</div></div>
<div class="card"><h4>Stock Value</h4><h1 style="font-size:26px;">₹4.6L</h1><div class="trend" style="color:var(--text-faint);">Current inventory</div></div>
</div>
<table>
<thead><tr><th>Part</th><th>Compatible Machine</th><th>In Stock</th><th>Reorder Level</th><th>Status</th></tr></thead>
<tbody>
<tr><td>Bearing 6205-ZZ</td><td>Machine 03</td><td>0</td><td>4</td><td><span class="status-pill down">Out of Stock</span></td></tr>
<tr><td>Drive Belt A-42</td><td>Machine 05</td><td>2</td><td>5</td><td><span class="status-pill idle">Low</span></td></tr>
<tr><td>Hydraulic Filter HF-12</td><td>Machine 02</td><td>3</td><td>6</td><td><span class="status-pill idle">Low</span></td></tr>
<tr><td>Motor Coupling MC-8</td><td>Machine 01</td><td>1</td><td>3</td><td><span class="status-pill idle">Low</span></td></tr>
<tr><td>Proximity Sensor PS-30</td><td>Machine 04</td><td>9</td><td>4</td><td><span class="status-pill running">Sufficient</span></td></tr>
</tbody>
</table>
</div>
<!-- Sub-tab: Machine Health -->
<div id="machineHealth" class="subtab-page">
<div class="panel" style="margin-top:0;">
<div class="panel-head">
<h3>Composite Health Score</h3>
<div class="tag">vibration · temperature · runtime</div>
</div>
<div class="gauge-row" id="gaugeRowHealth"></div>
</div>
<table>
<thead><tr><th>Machine</th><th>Vibration</th><th>Temperature</th><th>Runtime Hrs (30d)</th><th>Health</th></tr></thead>
<tbody>
<tr><td>Machine 01</td><td>Normal</td><td>52°C</td><td>612</td><td><span class="status-pill running">Good</span></td></tr>
<tr><td>Machine 02</td><td>Normal</td><td>55°C</td><td>598</td><td><span class="status-pill running">Good</span></td></tr>
<tr><td>Machine 03</td><td>High</td><td>71°C</td><td>640</td><td><span class="status-pill down">Critical</span></td></tr>
<tr><td>Machine 04</td><td>Slightly elevated</td><td>61°C</td><td>580</td><td><span class="status-pill idle">Watch</span></td></tr>
<tr><td>Machine 05</td><td>Normal</td><td>58°C</td><td>605</td><td><span class="status-pill idle">Watch</span></td></tr>
</tbody>
</table>
</div>
<!-- Sub-tab: Maintenance History -->
<div id="maintenanceHistory" class="subtab-page">
<div class="cards">
<div class="card">
<h4>Total Repairs</h4>
<h1>86</h1>
</div>
<div class="card">
<h4>Total Downtime</h4>
<h1>74 Hrs</h1>
</div>
<div class="card">
<h4>Most Failed Machine</h4>
<h1 style="font-size:24px;">Machine 03</h1>
</div>
<div class="card">
<h4>MTTR</h4>
<h1>48 Min</h1>
</div>
</div>
<table>
<thead>
<tr>
<th>Date</th>
<th>Machine</th>
<th>Issue</th>
<th>Downtime</th>
<th>Technician</th>
</tr>
</thead>
<tbody>
<tr>
<td>12 Jul 2026</td>
<td>Machine 01</td>
<td>Bearing Replacement</td>
<td>50 Min</td>
<td>Rajesh</td>
</tr>
<tr>
<td>11 Jul 2026</td>
<td>Machine 04</td>
<td>Motor Overheating</td>
<td>1 Hr 20 Min</td>
<td>Kumar</td>
</tr>
<tr>
<td>10 Jul 2026</td>
<td>Machine 02</td>
<td>PLC Fault</td>
<td>25 Min</td>
<td>Arun</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- ============ REPORTS ============ -->
<div id="reports" class="page">
<div class="section-head">
<div>
<div class="eyebrow">Reports</div>
<h2>Report Center</h2>
</div>
</div>
<div class="panel">
<div class="empty-state">
<i class="fa-solid fa-file-lines"></i>
<h3>No reports generated yet</h3>
<p>Daily, weekly, and shift-wise production reports will appear here once configured.</p>
</div>
</div>
</div>
<!-- ============ USERS ============ -->
<div id="users" class="page">
<div class="section-head">
<div>
<div class="eyebrow">Access</div>
<h2>User Management</h2>
</div>
</div>
<table>
<thead><tr><th>Name</th><th>Role</th><th>Status</th></tr></thead>
<tbody>
<tr><td>Admin</td><td>Administrator</td><td><span class="status-pill running">Active</span></td></tr>
<tr><td>Supervisor</td><td>Production</td><td><span class="status-pill running">Active</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<script>
/* ============ PAGE NAV ============ */
const pageTags = {
dashboard:'DASHBOARD OVERVIEW', production:'PRODUCTION', oee:'OEE ANALYSIS',
quality:'QUALITY CONTROL', maintenance:'MAINTENANCE', reports:'REPORTS', users:'USER MANAGEMENT'
};
function showSubtab(id, el){
const container = el.closest('.page');
container.querySelectorAll('.subtab-page').forEach(p=>p.classList.remove('active'));
container.querySelector('#'+id).classList.add('active');
container.querySelectorAll('.subtab-btn').forEach(b=>b.classList.remove('active'));
el.classList.add('active');
}
function showPage(id, el){
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
document.getElementById(id).classList.add('active');
document.querySelectorAll('.menu a').forEach(a=>a.classList.remove('active'));
el.classList.add('active');
document.getElementById('pageTag').textContent = pageTags[id] || '';
}
/* ============ CLOCK ============ */
function tickClock(){
const now = new Date();
const str = now.toLocaleTimeString('en-IN', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
document.getElementById('headerClock').textContent = str;
document.getElementById('footclock').textContent = str;
}
tickClock();
setInterval(tickClock, 1000);
/* ============ GAUGE BUILDER (signature element) ============ */
function buildGauge(percent, colorVar){
// semi-circular instrument gauge, 180deg sweep, tick marks + needle
const cx=75, cy=88, r=62;
const startAngle = 180, endAngle = 0; // sweep left to right over top
const toRad = d => d * Math.PI/180;
const pt = (ang,radius=r) => [cx + radius*Math.cos(toRad(ang)), cy - radius*Math.sin(toRad(ang))];
// arc path helper
function arcPath(a1,a2,radius){
const [x1,y1]=pt(a1,radius), [x2,y2]=pt(a2,radius);
const large = (a1-a2)>180 ? 1:0;
return `M ${x1} ${y1} A ${radius} ${radius} 0 ${large} 1 ${x2} ${y2}`;
}
// ticks
let ticks='';
for(let i=0;i<=10;i++){
const ang = 180 - i*18;
const p1 = pt(ang, r+2), p2 = pt(ang, i%5===0? r-9 : r-5);
ticks += `<line x1="${p1[0]}" y1="${p1[1]}" x2="${p2[0]}" y2="${p2[1]}" stroke="var(--steel)" stroke-width="${i%5===0?2:1}" stroke-linecap="round"/>`;
}
const valueAngle = 180 - (percent/100)*180;
const [nx,ny] = pt(valueAngle, r-14);
const trackPath = arcPath(180,0,r);
const fillPath = arcPath(180, valueAngle, r);
return `
<svg viewBox="0 0 150 96" xmlns="http://www.w3.org/2000/svg">
<path d="${trackPath}" fill="none" stroke="var(--panel-2)" stroke-width="9" stroke-linecap="round"/>
<path d="${fillPath}" fill="none" stroke="${colorVar}" stroke-width="9" stroke-linecap="round"/>
${ticks}
<circle cx="${cx}" cy="${cy}" r="4" fill="var(--text-dim)"/>
<line x1="${cx}" y1="${cy}" x2="${nx}" y2="${ny}" stroke="${colorVar}" stroke-width="2.5" stroke-linecap="round"/>
</svg>`;
}
function gaugeColor(pct){
if(pct>=90) return 'var(--green)';
if(pct>=75) return 'var(--amber)';
return 'var(--red)';
}
function renderGaugeRow(targetId, metrics){
const row = document.getElementById(targetId);
if(!row) return;
row.innerHTML = metrics.map(m=>{
const color = gaugeColor(m.value);
return `
<div class="gauge-card" style="--gauge-color:${color}">
<div class="g-label">${m.label}</div>
${buildGauge(m.value, color)}
<div class="g-value">${m.value}<span>%</span></div>
<div class="g-sub">${m.sub}</div>
</div>`;
}).join('');
}
const oeeMetrics = [
{label:'Availability', value:96, sub:'Target 92%'},
{label:'Performance', value:93, sub:'Target 90%'},
{label:'Quality', value:99, sub:'Target 97%'},
{label:'Overall OEE', value:88, sub:'Target 85%'}
];
renderGaugeRow('gaugeRow', oeeMetrics);
renderGaugeRow('gaugeRowOee', oeeMetrics);
/* ============ MACHINE STRIP ============ */
const machines = [
{name:'Machine 01', val:'1,450', unit:'units/shift', status:'running'},
{name:'Machine 02', val:'1,200', unit:'units/shift', status:'running'},
{name:'Machine 03', val:'0', unit:'stopped 14m', status:'down'},
{name:'Machine 04', val:'860', unit:'units/shift', status:'idle'},
{name:'Machine 05', val:'1,320', unit:'units/shift', status:'running'},
{name:'Machine 06', val:'1,510', unit:'units/shift', status:'running'},
];
document.getElementById('machineStrip').innerHTML = machines.map(m=>`
<div class="mchip ${m.status==='running'?'':'idle'}">
<div class="mtop"><span class="mname">${m.name}</span><span class="pulse" style="${m.status==='down'?'background:var(--red);':''}"></span></div>
<div class="mval">${m.val}</div>
<div class="mmeta">${m.unit}</div>
</div>
`).join('');
/* ============ CHARTS ============ */
Chart.defaults.color = '#8a97a8';
Chart.defaults.font.family = "'Inter',sans-serif";
const days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
new Chart(document.getElementById('oeeTrendChart'), {
type:'line',
data:{
labels:days,
datasets:[{
label:'OEE %',
data:[84,86,85,88,87,89,88],
borderColor:'#f2a93b',
backgroundColor:'rgba(242,169,59,0.12)',
fill:true, tension:.35, pointRadius:3, pointBackgroundColor:'#f2a93b', borderWidth:2.5
}]
},
options:{
responsive:true, maintainAspectRatio:false,
plugins:{legend:{display:false}},
scales:{
y:{min:70,max:100,grid:{color:'#1e2a3a'},ticks:{callback:v=>v+'%'}},
x:{grid:{display:false}}
}
}
});
new Chart(document.getElementById('apqChart'), {
type:'line',
data:{
labels:days,
datasets:[
{label:'Availability',data:[94,95,96,95,97,96,96],borderColor:'#4fa3e3',backgroundColor:'transparent',tension:.35,pointRadius:2,borderWidth:2},
{label:'Performance',data:[90,91,92,93,92,94,93],borderColor:'#f2a93b',backgroundColor:'transparent',tension:.35,pointRadius:2,borderWidth:2},
{label:'Quality',data:[98,98,99,99,98,99,99],borderColor:'#35c97a',backgroundColor:'transparent',tension:.35,pointRadius:2,borderWidth:2}
]
},
options:{
responsive:true, maintainAspectRatio:false,
plugins:{legend:{position:'top',labels:{boxWidth:10,padding:16}}},
scales:{
y:{min:80,max:100,grid:{color:'#1e2a3a'},ticks:{callback:v=>v+'%'}},
x:{grid:{display:false}}
}
}
});
new Chart(document.getElementById('qualityChart'), {
type:'bar',
data:{
labels:days,
datasets:[{
label:'Defect rate (ppm)',
data:[420,380,410,350,300,290,260],
backgroundColor:'#e8604a',
borderRadius:4,
maxBarThickness:34
}]
},
options:{
responsive:true, maintainAspectRatio:false,
plugins:{legend:{display:false}},
scales:{
y:{grid:{color:'#1e2a3a'}},
x:{grid:{display:false}}
}
}
});
const themeBtn = document.getElementById("themeToggle");
function updateChartTheme(){
if (typeof Chart === "undefined") {
return;
}
if(document.body.classList.contains("light-theme")){
Chart.defaults.color = "#475569";
}else{
Chart.defaults.color = "#8a97a8";
}
}
themeBtn.addEventListener("click", () => {
document.body.classList.toggle("light-theme");
updateChartTheme();
if(document.body.classList.contains("light-theme")){
themeBtn.innerHTML = '<i class="fa-solid fa-sun"></i>';
}else{
themeBtn.innerHTML = '<i class="fa-solid fa-moon"></i>';
}
});
function openBreakdownModal(){
document.getElementById("breakdownModal").style.display="block";
}
function closeBreakdownModal(){
document.getElementById("breakdownModal").style.display="none";
}
window.onclick=function(event){
const modal=document.getElementById("breakdownModal");
if(event.target===modal){
modal.style.display="none";
}
}
function saveBreakdown() {
const data = {
plant_id: document.getElementById("plant_id").value,
line_id: document.getElementById("line_id").value,
machine_name: document.getElementById("machine_name").value,
breakdown_date: document.getElementById("breakdown_date").value,
breakdown_time: document.getElementById("breakdown_time").value,
down_time: document.getElementById("down_time").value,
reason: document.getElementById("reason").value
};
fetch("/save-breakdown/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": getCookie("csrftoken")
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if(result.status === "success"){
alert("Breakdown saved successfully");
closeBreakdownModal();
location.reload();
}else{
alert(result.message);
}
});
}
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
document.addEventListener("DOMContentLoaded", function () {
const plantDropdown = document.getElementById("plant_id");
const lineDropdown = document.getElementById("line_id");
plantDropdown.addEventListener("change", function () {
const plantId = this.value;
lineDropdown.innerHTML =
'<option value="">Loading...</option>';
if (plantId === "") {
lineDropdown.innerHTML =
'<option value="">Select Line</option>';
return;
}
fetch("/get-lines/" + plantId + "/")
.then(function(response){
if(!response.ok){
throw new Error("Network Error");
}
return response.json();
})
.then(function(data){
lineDropdown.innerHTML =
'<option value="">Select Line</option>';
data.lines.forEach(function(line){
const option = document.createElement("option");
option.value = line.id;
option.text = line.name;
lineDropdown.appendChild(option);
});
})
.catch(function(error){
console.log(error);
lineDropdown.innerHTML =
'<option value="">Unable to load lines</option>';
});
});
});
</script>
</body>
</html>