38815-vm/dashboard.php
2026-02-28 22:33:39 +00:00

464 lines
17 KiB
PHP

<?php
session_start();
require 'db/config.php';
$db = db();
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$role = $_SESSION['role'];
$userName = $_SESSION['user_name'];
$userId = $_SESSION['user_id'];
// Get assigned processes for worker
$assignedProcesses = [];
if ($role === 'worker') {
$stmt = $db->prepare("SELECT assigned_processes FROM users WHERE id = ?");
$stmt->execute([$userId]);
$res = $stmt->fetch();
$assignedProcesses = json_decode($res['assigned_processes'] ?? '[]', true);
}
// Stats for Admin
$stats = [
'active_jobs' => 0,
'pending_ops' => 0,
'inventory_alerts' => 0,
'active_workers' => 0
];
if ($role === 'admin') {
$stats['active_jobs'] = $db->query("SELECT COUNT(*) FROM jobs WHERE status = 'in_progress'")->fetchColumn();
$stats['pending_ops'] = $db->query("SELECT COUNT(*) FROM operations WHERE status = 'pending'")->fetchColumn();
$stats['inventory_alerts'] = $db->query("SELECT COUNT(*) FROM inventory WHERE stock_level <= reorder_level")->fetchColumn();
$stats['active_workers'] = $db->query("SELECT COUNT(DISTINCT assigned_worker_id) FROM operations WHERE status = 'in_progress'")->fetchColumn();
// Fetch recent jobs
$recentJobs = $db->query("SELECT * FROM jobs ORDER BY created_at DESC LIMIT 5")->fetchAll();
}
// Queue for Worker or Admin View
$queue = [];
if ($role === 'worker') {
if (!empty($assignedProcesses)) {
$placeholders = implode(',', array_fill(0, count($assignedProcesses), '?'));
$stmt = $db->prepare("
SELECT o.*, c.name as component_name, j.name as job_name
FROM operations o
JOIN components c ON o.component_id = c.id
JOIN jobs j ON c.job_id = j.id
WHERE o.status IN ('pending', 'in_progress', 'stalled')
AND o.process_type IN ($placeholders)
ORDER BY o.priority DESC, o.created_at ASC
");
$stmt->execute($assignedProcesses);
$queue = $stmt->fetchAll();
}
} else {
// Admin sees all in-progress or stalled ops
$queue = $db->query("
SELECT o.*, c.name as component_name, j.name as job_name, u.name as worker_name
FROM operations o
JOIN components c ON o.component_id = c.id
JOIN jobs j ON c.job_id = j.id
LEFT JOIN users u ON o.assigned_worker_id = u.id
WHERE o.status IN ('in_progress', 'stalled')
ORDER BY o.created_at ASC
")->fetchAll();
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>M-TRACK | Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--sidebar-width: 240px;
--bg: #f8fafc;
--primary: #1e293b;
--accent: #3b82f6;
--text: #334155;
--border: #e2e8f0;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: var(--bg);
color: var(--text);
}
.sidebar {
width: var(--sidebar-width);
position: fixed;
top: 0;
bottom: 0;
left: 0;
background-color: var(--primary);
color: white;
z-index: 1000;
padding: 1.5rem 1rem;
}
.sidebar h2 {
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 2rem;
padding: 0 0.5rem;
letter-spacing: -0.025em;
}
.nav-pills .nav-link {
color: #94a3b8;
font-weight: 500;
font-size: 0.875rem;
padding: 0.625rem 0.75rem;
margin-bottom: 0.25rem;
border-radius: 4px;
}
.nav-pills .nav-link:hover {
color: white;
background-color: rgba(255,255,255,0.05);
}
.nav-pills .nav-link.active {
color: white;
background-color: var(--accent);
}
.main-content {
margin-left: var(--sidebar-width);
padding: 2.5rem;
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.top-bar h1 {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary);
margin: 0;
}
.user-pill {
background: white;
border: 1px solid var(--border);
padding: 0.375rem 1rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
.card {
background: white;
border: 1px solid var(--border);
border-radius: 4px;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
margin-bottom: 1.5rem;
}
.card-header {
background: white;
border-bottom: 1px solid var(--border);
padding: 1rem 1.25rem;
font-weight: 600;
font-size: 0.875rem;
color: var(--primary);
}
.badge-process {
background: #e2e8f0;
color: #475569;
font-weight: 600;
font-size: 0.75rem;
padding: 0.25rem 0.625rem;
border-radius: 4px;
margin-right: 0.5rem;
}
.op-card {
padding: 1.25rem;
border-left: 4px solid #cbd5e1;
transition: transform 0.1s;
}
.op-card:hover {
transform: translateY(-2px);
}
.op-card.status-in_progress { border-left-color: var(--success); }
.op-card.status-stalled { border-left-color: var(--warning); }
.op-card.status-pending { border-left-color: #cbd5e1; }
.op-meta {
font-size: 0.75rem;
color: #64748b;
margin-bottom: 0.25rem;
text-transform: uppercase;
letter-spacing: 0.025em;
font-weight: 600;
}
.op-title {
font-weight: 700;
font-size: 1rem;
color: var(--primary);
margin-bottom: 0.5rem;
}
.op-job {
font-size: 0.875rem;
color: #64748b;
}
.btn-action {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
padding: 0.5rem 1rem;
}
</style>
</head>
<body>
<div class="sidebar">
<h2>M-TRACK</h2>
<nav class="nav nav-pills flex-column">
<a class="nav-link active" href="dashboard.php"><i class="bi bi-grid-fill me-2"></i> Dashboard</a>
<?php if ($role === 'admin'): ?>
<a class="nav-link" href="jobs.php"><i class="bi bi-briefcase me-2"></i> Jobs</a>
<a class="nav-link" href="shop_floor.php"><i class="bi bi-kanban me-2"></i> Shop Floor</a>
<a class="nav-link" href="inventory.php"><i class="bi bi-boxes me-2"></i> Inventory</a>
<a class="nav-link" href="users.php"><i class="bi bi-people me-2"></i> Users</a>
<a class="nav-link" href="time_study.php"><i class="bi bi-clock-history me-2"></i> Time Study</a>
<a class="nav-link" href="scan.php"><i class="bi bi-upc-scan me-2"></i> Scan</a>
<?php else: ?>
<a class="nav-link" href="dashboard.php"><i class="bi bi-list-task me-2"></i> My Queue</a>
<a class="nav-link" href="shop_floor.php"><i class="bi bi-kanban me-2"></i> Board View</a>
<a class="nav-link" href="scan.php"><i class="bi bi-upc-scan me-2"></i> Scan</a>
<?php endif; ?>
<hr class="my-4 border-secondary opacity-25">
<a class="nav-link text-danger" href="logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a>
</nav>
</div>
<div class="main-content">
<div class="top-bar">
<h1><?= $role === 'admin' ? 'Operations Overview' : 'Work Queue' ?></h1>
<div class="user-pill">
<i class="bi bi-person-circle text-muted"></i>
<?= htmlspecialchars($userName) ?>
<span class="text-muted text-uppercase" style="font-size: 0.625rem;"><?= $role ?></span>
</div>
</div>
<?php if ($role === 'worker'): ?>
<div class="mb-4 d-flex align-items-center">
<span class="text-muted small fw-bold text-uppercase me-3">My Skills:</span>
<?php foreach ($assignedProcesses as $proc): ?>
<span class="badge-process"><?= strtoupper($proc) ?></span>
<?php endforeach; ?>
</div>
<div class="row">
<?php if (empty($queue)): ?>
<div class="col-12">
<div class="card card-body py-5 text-center">
<div class="mb-3 text-muted" style="font-size: 2rem;"><i class="bi bi-clipboard-check"></i></div>
<h5 class="text-muted">No pending operations for your assigned processes.</h5>
</div>
</div>
<?php else: ?>
<?php foreach ($queue as $op): ?>
<div class="col-md-6 col-xl-4">
<div class="card op-card status-<?= $op['status'] ?>">
<div class="op-meta"><?= $op['process_type'] ?> • Priority <?= $op['priority'] ?></div>
<div class="op-title"><?= htmlspecialchars($op['name']) ?></div>
<div class="op-job mb-3">
<i class="bi bi-box-seam me-1"></i> <?= htmlspecialchars($op['job_name']) ?> / <?= htmlspecialchars($op['component_name']) ?>
</div>
<div class="d-flex gap-2">
<?php if ($op['status'] === 'pending' || $op['status'] === 'stalled'): ?>
<button class="btn btn-success btn-action w-100" onclick="handleOp(<?= $op['id'] ?>, 'start')">
<?= $op['status'] === 'stalled' ? 'Resume' : 'Start' ?>
</button>
<?php endif; ?>
<?php if ($op['status'] === 'in_progress'): ?>
<button class="btn btn-warning btn-action w-50" onclick="handleOp(<?= $op['id'] ?>, 'stall')">Stall</button>
<button class="btn btn-primary btn-action w-50" onclick="handleOp(<?= $op['id'] ?>, 'done')">Done</button>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php else: ?>
<!-- Admin View -->
<div class="row">
<div class="col-md-3">
<div class="card text-center p-3">
<div class="text-muted small fw-bold text-uppercase mb-1">Active Jobs</div>
<div class="h3 fw-bold mb-0"><?= $stats['active_jobs'] ?></div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="text-muted small fw-bold text-uppercase mb-1">Pending Ops</div>
<div class="h3 fw-bold mb-0"><?= $stats['pending_ops'] ?></div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="text-muted small fw-bold text-uppercase mb-1">Inventory Alerts</div>
<div class="h3 fw-bold mb-0 text-<?= $stats['inventory_alerts'] > 0 ? 'danger' : 'success' ?>">
<?= $stats['inventory_alerts'] ?>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center p-3">
<div class="text-muted small fw-bold text-uppercase mb-1">Active Workers</div>
<div class="h3 fw-bold mb-0"><?= $stats['active_workers'] ?></div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
Live Production Status
<span class="badge bg-primary rounded-pill"><?= count($queue) ?> Active Ops</span>
</div>
<div class="card-body p-0">
<?php if (empty($queue)): ?>
<div class="p-5 text-center text-muted">No operations currently in progress.</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Operation</th>
<th>Worker</th>
<th>Status</th>
<th>Started</th>
</tr>
</thead>
<tbody>
<?php foreach ($queue as $op): ?>
<tr>
<td>
<div class="fw-bold"><?= htmlspecialchars($op['name']) ?></div>
<div class="small text-muted"><?= htmlspecialchars($op['job_name']) ?></div>
</td>
<td><?= htmlspecialchars($op['worker_name'] ?? 'Unassigned') ?></td>
<td>
<span class="badge bg-<?= $op['status'] === 'in_progress' ? 'success' : 'warning' ?>">
<?= strtoupper($op['status']) ?>
</span>
</td>
<td class="small"><?= date('H:i', strtotime($op['start_time'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">Recent Jobs</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<?php foreach ($recentJobs as $job): ?>
<div class="list-group-item">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="fw-bold"><?= htmlspecialchars($job['name']) ?></span>
<span class="badge bg-light text-dark border small"><?= $job['status'] ?></span>
</div>
<div class="small text-muted">Due: <?= $job['due_date'] ?> • Qty: <?= $job['quantity'] ?></div>
</div>
<?php endforeach; ?>
<?php if (empty($recentJobs)): ?>
<div class="p-4 text-center text-muted">No jobs created yet.</div>
<?php endif; ?>
</div>
</div>
<div class="card-footer bg-white">
<a href="jobs.php" class="btn btn-sm btn-outline-primary w-100">Manage Jobs</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<!-- Modal for Stall Reason -->
<div class="modal fade" id="stallModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Stall Reason</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<textarea id="stallReason" class="form-control" placeholder="Explain why work is stalling (e.g., missing hardware, machine down)..."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-warning" onclick="confirmStall()">Confirm Stall</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
let currentOpId = null;
const stallModal = new bootstrap.Modal(document.getElementById('stallModal'));
function handleOp(opId, action) {
if (action === 'stall') {
currentOpId = opId;
stallModal.show();
return;
}
if (action === 'done' && !confirm('Mark this operation as complete?')) return;
performAction(opId, action);
}
function confirmStall() {
const reason = document.getElementById('stallReason').value.trim();
if (!reason) {
alert('Please provide a reason for stalling.');
return;
}
performAction(currentOpId, 'stall', reason);
stallModal.hide();
}
function performAction(opId, action, reason = '') {
fetch('api/ops.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ opId, action, reason })
})
.then(r => r.json())
.then(res => {
if (res.success) {
location.reload();
} else {
alert('Error: ' + res.error);
}
});
}
</script>
</body>
</html>