diff --git a/api/ops.php b/api/ops.php new file mode 100644 index 0000000..c0d92db --- /dev/null +++ b/api/ops.php @@ -0,0 +1,115 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +$userId = $_SESSION['user_id']; +$data = json_decode(file_get_contents('php://input'), true); + +if (!$data || !isset($data['opId']) || !isset($data['action'])) { + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'error' => 'Invalid data']); + exit; +} + +$opId = $data['opId']; +$action = $data['action']; +$reason = $data['reason'] ?? null; + +try { + $db->beginTransaction(); + + // Fetch current op state + $stmt = $db->prepare("SELECT * FROM operations WHERE id = ?"); + $stmt->execute([$opId]); + $op = $stmt->fetch(); + + if (!$op) { + throw new Exception("Operation not found"); + } + + $eventType = ''; + $statusUpdate = ''; + $now = date('Y-m-d H:i:s'); + + switch ($action) { + case 'start': + $statusUpdate = "status = 'in_progress', assigned_worker_id = $userId, start_time = IFNULL(start_time, '$now')"; + $eventType = 'start'; + // If it was stalled, event type is 'resume' + if ($op['status'] === 'stalled') { + $eventType = 'resume'; + } + break; + + case 'stall': + $statusUpdate = "status = 'stalled', stall_reason = " . $db->quote($reason); + $eventType = 'stalled'; + break; + + case 'done': + $statusUpdate = "status = 'completed', end_time = '$now'"; + $eventType = 'completed'; + break; + + default: + throw new Exception("Invalid action"); + } + + // Update operation + $db->exec("UPDATE operations SET $statusUpdate WHERE id = $opId"); + + // Log time study event + $stmt = $db->prepare("INSERT INTO time_study_events (operation_id, user_id, event_type, reason) VALUES (?, ?, ?, ?)"); + $stmt->execute([$opId, $userId, $eventType, $reason]); + + // Post-completion logic (if done) + if ($action === 'done') { + // Check if all operations for this component are done + $compId = $op['component_id']; + $pendingOps = $db->prepare("SELECT COUNT(*) FROM operations WHERE component_id = ? AND status != 'completed'"); + $pendingOps->execute([$compId]); + if ($pendingOps->fetchColumn() == 0) { + // Component is done + $db->exec("UPDATE components SET status = 'completed' WHERE id = $compId"); + + // Check if all components for this job are done + $stmt = $db->prepare("SELECT job_id FROM components WHERE id = ?"); + $stmt->execute([$compId]); + $jobId = $stmt->fetchColumn(); + + $pendingComps = $db->prepare("SELECT COUNT(*) FROM components WHERE job_id = ? AND status != 'completed'"); + $pendingComps->execute([$jobId]); + if ($pendingComps->fetchColumn() == 0) { + // Job is done + $db->exec("UPDATE jobs SET status = 'completed' WHERE id = $jobId"); + } else { + // Job is in_progress if not already + $db->exec("UPDATE jobs SET status = 'in_progress' WHERE id = $jobId"); + } + } else { + // Component is in_progress if not already + $db->exec("UPDATE components SET status = 'in_progress' WHERE id = $compId"); + + // Job is in_progress + $stmt = $db->prepare("SELECT job_id FROM components WHERE id = ?"); + $stmt->execute([$compId]); + $jobId = $stmt->fetchColumn(); + $db->exec("UPDATE jobs SET status = 'in_progress' WHERE id = $jobId"); + } + } + + $db->commit(); + echo json_encode(['success' => true]); + +} catch (Exception $e) { + $db->rollBack(); + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/dashboard.php b/dashboard.php index 64b800f..8dea93e 100644 --- a/dashboard.php +++ b/dashboard.php @@ -21,6 +21,54 @@ if ($role === 'worker') { $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(); +} + ?> @@ -39,6 +87,9 @@ if ($role === 'worker') { --accent: #3b82f6; --text: #334155; --border: #e2e8f0; + --success: #10b981; + --warning: #f59e0b; + --danger: #ef4444; } body { font-family: 'Inter', system-ui, -apple-system, sans-serif; @@ -130,6 +181,42 @@ if ($role === 'worker') { 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; + }
@@ -137,13 +224,13 @@ if ($role === 'worker') { +New jobs will appear here when ready for production.
| Operation | +Worker | +Status | +Started | +
|---|---|---|---|
|
+ = htmlspecialchars($op['name']) ?>
+ = htmlspecialchars($op['job_name']) ?>
+ |
+ = htmlspecialchars($op['worker_name'] ?? 'Unassigned') ?> | ++ + = strtoupper($op['status']) ?> + + | += date('H:i', strtotime($op['start_time'])) ?> | +