json(['ok' => false, 'error' => 'Unauthorized'], 401); } $boardId = (int) $request->input('board_id', 0); $name = trim((string) $request->input('name', '')); if ($boardId === 0 || $name === '') { return $this->json(['ok' => false, 'error' => 'board_id and name are required']); } $now = date('Y-m-d H:i:s'); $username = AuthService::getCurrentUsername(); $lane = new SwimLane(); $lane->boardId = $boardId; $lane->name = $name; $lane->position = $this->lanes()->maxPosition($boardId) + 1; $lane->createdAt = $now; $lane->createdBy = $username; $lane->updatedAt = $now; $lane->updatedBy = $username; $this->lanes()->insert($lane); return $this->json(['ok' => true, 'id' => $lane->id, 'name' => $lane->name, 'position' => $lane->position]); } public function update(Request $request, int $id): mixed { if (!AuthService::isLoggedIn()) { return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); } $name = trim((string) $request->input('name', '')); if ($name === '') { return $this->json(['ok' => false, 'error' => 'name is required']); } $row = $this->lanes()->find($id); if ($row === null) { return $this->json(['ok' => false, 'error' => 'Not found'], 404); } $lane = SwimLane::fromRow($row); $lane->name = $name; $lane->updatedAt = date('Y-m-d H:i:s'); $lane->updatedBy = AuthService::getCurrentUsername(); $this->lanes()->update($lane); return $this->json(['ok' => true]); } public function toggleExport(Request $request, int $id): mixed { if (!AuthService::isLoggedIn()) { return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); } $row = $this->lanes()->find($id); if ($row === null) { return $this->json(['ok' => false, 'error' => 'Not found'], 404); } $show = (string) $request->input('show_export_button', '0') === '1'; $this->lanes()->updateShowExportButton($id, $show, date('Y-m-d H:i:s'), AuthService::getCurrentUsername()); return $this->json(['ok' => true, 'show_export_button' => $show]); } public function updateCardAgeSettings(Request $request, int $id): mixed { if (!AuthService::isLoggedIn()) { return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); } $row = $this->lanes()->find($id); if ($row === null) { return $this->json(['ok' => false, 'error' => 'Not found'], 404); } $showCardAge = (string) $request->input('show_card_age', '0') === '1'; $cardAgeWarningDays = max(0, (int) $request->input('card_age_warning_days', 0)); $this->lanes()->updateCardAgeSettings( $id, $showCardAge, $cardAgeWarningDays, date('Y-m-d H:i:s'), AuthService::getCurrentUsername() ); return $this->json([ 'ok' => true, 'show_card_age' => $showCardAge, 'card_age_warning_days' => $cardAgeWarningDays, ]); } public function export(int $id): mixed { if (!AuthService::isLoggedIn()) { return $this->redirect('/auth/login'); } $row = $this->lanes()->find($id); if ($row === null) { return new \Core\Response('Not found', 404); } $lane = \App\Models\SwimLane::fromRow($row); $cards = $this->cards()->findBySwimLaneId($id); $out = fopen('php://memory', 'wb'); fputcsv($out, ['Job #', 'Job Name', 'Customer', 'Delivery Date', 'Quantity', 'Notes'], ',', '"', '\\'); foreach ($cards as $card) { fputcsv($out, [ $card->jobNumber, $card->jobName, $card->customerName, $card->deliveryDate ?? '', $card->quantity ?? '', $card->notes, ], ',', '"', '\\'); } rewind($out); $csv = (string) stream_get_contents($out); fclose($out); $slug = trim((string) preg_replace('/[^a-z0-9]+/i', '-', $lane->name), '-'); $filename = ($slug ?: 'lane') . '-' . date('Y-m-d') . '.csv'; return new \Core\Response($csv, 200, [ 'Content-Type' => 'text/csv; charset=utf-8', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ]); } public function destroy(int $id): mixed { if (!AuthService::isLoggedIn()) { return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); } $this->cards()->deleteBySwimLaneId($id); $this->lanes()->delete($id); return $this->json(['ok' => true]); } public function reorder(): mixed { if (!AuthService::isLoggedIn()) { return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); } $raw = file_get_contents('php://input'); $items = json_decode((string) $raw, true); if (!is_array($items)) { return $this->json(['ok' => false, 'error' => 'Invalid JSON payload']); } $now = date('Y-m-d H:i:s'); $username = AuthService::getCurrentUsername(); foreach ($items as $item) { $laneId = (int) ($item['id'] ?? 0); $position = (int) ($item['position'] ?? 0); if ($laneId > 0) { $this->lanes()->updatePosition($laneId, $position, $now, $username); } } return $this->json(['ok' => true]); } }