diff --git a/app/Controllers/BoardsController.php b/app/Controllers/BoardsController.php index 411fa68..f21535d 100644 --- a/app/Controllers/BoardsController.php +++ b/app/Controllers/BoardsController.php @@ -173,35 +173,6 @@ class BoardsController extends Controller return $this->redirect('/board/' . $board->slug); } - public function updateCardAgeSettings(Request $request, int $id): mixed - { - if (!AuthService::isLoggedIn()) { - return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); - } - - $row = $this->boards()->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->boards()->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 destroy(string $slug): mixed { if ($guard = AuthService::requireLogin()) { diff --git a/app/Controllers/ColumnsController.php b/app/Controllers/ColumnsController.php index de0641e..5c0038c 100644 --- a/app/Controllers/ColumnsController.php +++ b/app/Controllers/ColumnsController.php @@ -97,6 +97,35 @@ class ColumnsController extends Controller return $this->json(['ok' => true, 'show_card_count' => $show]); } + public function updateCardAgeSettings(Request $request, int $id): mixed + { + if (!AuthService::isLoggedIn()) { + return $this->json(['ok' => false, 'error' => 'Unauthorized'], 401); + } + + $row = $this->columns()->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->columns()->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 destroy(int $id): mixed { if (!AuthService::isLoggedIn()) { diff --git a/app/Controllers/SwimLanesController.php b/app/Controllers/SwimLanesController.php index 4ebc01b..e8f2c29 100644 --- a/app/Controllers/SwimLanesController.php +++ b/app/Controllers/SwimLanesController.php @@ -79,6 +79,35 @@ class SwimLanesController extends Controller return $this->json(['ok' => true]); } + 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 destroy(int $id): mixed { if (!AuthService::isLoggedIn()) { diff --git a/app/Models/Board.php b/app/Models/Board.php index 5c63830..16b8125 100644 --- a/app/Models/Board.php +++ b/app/Models/Board.php @@ -11,8 +11,6 @@ class Board public string $slug = ''; public bool $importFromPrintstream = false; public string $printstreamJobName = ''; - public bool $showCardAge = false; - public int $cardAgeWarningDays = 0; public ?string $createdAt = null; public string $createdBy = ''; public ?string $updatedAt = null; @@ -26,8 +24,6 @@ class Board $model->slug = (string) ($row['slug'] ?? ''); $model->importFromPrintstream = (bool) ($row['import_from_printstream'] ?? false); $model->printstreamJobName = (string) ($row['printstream_job_name'] ?? ''); - $model->showCardAge = (bool) ($row['show_card_age'] ?? false); - $model->cardAgeWarningDays = (int) ($row['card_age_warning_days'] ?? 0); $model->createdAt = $row['created_at'] ?? null; $model->createdBy = (string) ($row['created_by'] ?? ''); $model->updatedAt = $row['updated_at'] ?? null; diff --git a/app/Models/BoardColumn.php b/app/Models/BoardColumn.php index 181b2a7..295b1c8 100644 --- a/app/Models/BoardColumn.php +++ b/app/Models/BoardColumn.php @@ -11,6 +11,8 @@ class BoardColumn public string $name = ''; public int $position = 0; public bool $showCardCount = false; + public bool $showCardAge = false; + public int $cardAgeWarningDays = 0; public ?string $createdAt = null; public string $createdBy = ''; public ?string $updatedAt = null; @@ -19,15 +21,17 @@ class BoardColumn public static function fromRow(array $row): self { $model = new self(); - $model->id = (int) ($row['id'] ?? 0); - $model->boardId = (int) ($row['board_id'] ?? 0); - $model->name = (string) ($row['name'] ?? ''); - $model->position = (int) ($row['position'] ?? 0); - $model->showCardCount = (bool) ($row['show_card_count'] ?? false); - $model->createdAt = $row['created_at'] ?? null; - $model->createdBy = (string) ($row['created_by'] ?? ''); - $model->updatedAt = $row['updated_at'] ?? null; - $model->updatedBy = (string) ($row['updated_by'] ?? ''); + $model->id = (int) ($row['id'] ?? 0); + $model->boardId = (int) ($row['board_id'] ?? 0); + $model->name = (string) ($row['name'] ?? ''); + $model->position = (int) ($row['position'] ?? 0); + $model->showCardCount = (bool) ($row['show_card_count'] ?? false); + $model->showCardAge = (bool) ($row['show_card_age'] ?? false); + $model->cardAgeWarningDays = (int) ($row['card_age_warning_days'] ?? 0); + $model->createdAt = $row['created_at'] ?? null; + $model->createdBy = (string) ($row['created_by'] ?? ''); + $model->updatedAt = $row['updated_at'] ?? null; + $model->updatedBy = (string) ($row['updated_by'] ?? ''); return $model; } diff --git a/app/Models/SwimLane.php b/app/Models/SwimLane.php index 4978294..4910aa7 100644 --- a/app/Models/SwimLane.php +++ b/app/Models/SwimLane.php @@ -10,6 +10,8 @@ class SwimLane public int $boardId = 0; public string $name = ''; public int $position = 0; + public bool $showCardAge = false; + public int $cardAgeWarningDays = 0; public ?string $createdAt = null; public string $createdBy = ''; public ?string $updatedAt = null; @@ -18,14 +20,16 @@ class SwimLane public static function fromRow(array $row): self { $model = new self(); - $model->id = (int) ($row['id'] ?? 0); - $model->boardId = (int) ($row['board_id'] ?? 0); - $model->name = (string) ($row['name'] ?? ''); - $model->position = (int) ($row['position'] ?? 0); - $model->createdAt = $row['created_at'] ?? null; - $model->createdBy = (string) ($row['created_by'] ?? ''); - $model->updatedAt = $row['updated_at'] ?? null; - $model->updatedBy = (string) ($row['updated_by'] ?? ''); + $model->id = (int) ($row['id'] ?? 0); + $model->boardId = (int) ($row['board_id'] ?? 0); + $model->name = (string) ($row['name'] ?? ''); + $model->position = (int) ($row['position'] ?? 0); + $model->showCardAge = (bool) ($row['show_card_age'] ?? false); + $model->cardAgeWarningDays = (int) ($row['card_age_warning_days'] ?? 0); + $model->createdAt = $row['created_at'] ?? null; + $model->createdBy = (string) ($row['created_by'] ?? ''); + $model->updatedAt = $row['updated_at'] ?? null; + $model->updatedBy = (string) ($row['updated_by'] ?? ''); return $model; } diff --git a/app/Repositories/BoardColumnRepository.php b/app/Repositories/BoardColumnRepository.php index 5dd5e53..00e06e5 100644 --- a/app/Repositories/BoardColumnRepository.php +++ b/app/Repositories/BoardColumnRepository.php @@ -35,17 +35,19 @@ class BoardColumnRepository extends Repository public function insert(BoardColumn $col): BoardColumn { $this->database->execute( - 'INSERT INTO board_columns (board_id, name, position, show_card_count, created_at, created_by, updated_at, updated_by) - VALUES (:board_id, :name, :position, :show_card_count, :created_at, :created_by, :updated_at, :updated_by)', + 'INSERT INTO board_columns (board_id, name, position, show_card_count, show_card_age, card_age_warning_days, created_at, created_by, updated_at, updated_by) + VALUES (:board_id, :name, :position, :show_card_count, :show_card_age, :card_age_warning_days, :created_at, :created_by, :updated_at, :updated_by)', [ - 'board_id' => $col->boardId, - 'name' => $col->name, - 'position' => $col->position, - 'show_card_count' => $col->showCardCount ? 1 : 0, - 'created_at' => $col->createdAt, - 'created_by' => $col->createdBy, - 'updated_at' => $col->updatedAt, - 'updated_by' => $col->updatedBy, + 'board_id' => $col->boardId, + 'name' => $col->name, + 'position' => $col->position, + 'show_card_count' => $col->showCardCount ? 1 : 0, + 'show_card_age' => $col->showCardAge ? 1 : 0, + 'card_age_warning_days' => $col->cardAgeWarningDays, + 'created_at' => $col->createdAt, + 'created_by' => $col->createdBy, + 'updated_at' => $col->updatedAt, + 'updated_by' => $col->updatedBy, ] ); @@ -71,6 +73,21 @@ class BoardColumnRepository extends Repository ); } + public function updateCardAgeSettings(int $id, bool $showCardAge, int $cardAgeWarningDays, string $updatedAt, string $updatedBy): void + { + $this->database->execute( + 'UPDATE board_columns SET show_card_age = :show_card_age, card_age_warning_days = :card_age_warning_days, + updated_at = :updated_at, updated_by = :updated_by WHERE id = :id', + [ + 'show_card_age' => $showCardAge ? 1 : 0, + 'card_age_warning_days' => $cardAgeWarningDays, + 'updated_at' => $updatedAt, + 'updated_by' => $updatedBy, + 'id' => $id, + ] + ); + } + public function updatePosition(int $id, int $position, string $updatedAt, string $updatedBy): void { $this->database->execute( diff --git a/app/Repositories/BoardRepository.php b/app/Repositories/BoardRepository.php index 2e69d94..f565341 100644 --- a/app/Repositories/BoardRepository.php +++ b/app/Repositories/BoardRepository.php @@ -63,18 +63,14 @@ class BoardRepository extends Repository { $this->database->execute( 'INSERT INTO boards - (name, slug, import_from_printstream, printstream_job_name, show_card_age, card_age_warning_days, - created_at, created_by, updated_at, updated_by) + (name, slug, import_from_printstream, printstream_job_name, created_at, created_by, updated_at, updated_by) VALUES - (:name, :slug, :import_from_printstream, :printstream_job_name, :show_card_age, :card_age_warning_days, - :created_at, :created_by, :updated_at, :updated_by)', + (:name, :slug, :import_from_printstream, :printstream_job_name, :created_at, :created_by, :updated_at, :updated_by)', [ 'name' => $board->name, 'slug' => $board->slug, 'import_from_printstream' => $board->importFromPrintstream ? 1 : 0, 'printstream_job_name' => $board->printstreamJobName, - 'show_card_age' => $board->showCardAge ? 1 : 0, - 'card_age_warning_days' => $board->cardAgeWarningDays, 'created_at' => $board->createdAt, 'created_by' => $board->createdBy, 'updated_at' => $board->updatedAt, @@ -106,21 +102,4 @@ class BoardRepository extends Repository ] ); } - - public function updateCardAgeSettings(int $id, bool $showCardAge, int $cardAgeWarningDays, string $updatedAt, string $updatedBy): void - { - $this->database->execute( - 'UPDATE boards - SET show_card_age = :show_card_age, card_age_warning_days = :card_age_warning_days, - updated_at = :updated_at, updated_by = :updated_by - WHERE id = :id', - [ - 'show_card_age' => $showCardAge ? 1 : 0, - 'card_age_warning_days' => $cardAgeWarningDays, - 'updated_at' => $updatedAt, - 'updated_by' => $updatedBy, - 'id' => $id, - ] - ); - } } diff --git a/app/Repositories/SwimLaneRepository.php b/app/Repositories/SwimLaneRepository.php index 5bec01f..0c1ec71 100644 --- a/app/Repositories/SwimLaneRepository.php +++ b/app/Repositories/SwimLaneRepository.php @@ -35,16 +35,18 @@ class SwimLaneRepository extends Repository public function insert(SwimLane $lane): SwimLane { $this->database->execute( - 'INSERT INTO swim_lanes (board_id, name, position, created_at, created_by, updated_at, updated_by) - VALUES (:board_id, :name, :position, :created_at, :created_by, :updated_at, :updated_by)', + 'INSERT INTO swim_lanes (board_id, name, position, show_card_age, card_age_warning_days, created_at, created_by, updated_at, updated_by) + VALUES (:board_id, :name, :position, :show_card_age, :card_age_warning_days, :created_at, :created_by, :updated_at, :updated_by)', [ - 'board_id' => $lane->boardId, - 'name' => $lane->name, - 'position' => $lane->position, - 'created_at' => $lane->createdAt, - 'created_by' => $lane->createdBy, - 'updated_at' => $lane->updatedAt, - 'updated_by' => $lane->updatedBy, + 'board_id' => $lane->boardId, + 'name' => $lane->name, + 'position' => $lane->position, + 'show_card_age' => $lane->showCardAge ? 1 : 0, + 'card_age_warning_days' => $lane->cardAgeWarningDays, + 'created_at' => $lane->createdAt, + 'created_by' => $lane->createdBy, + 'updated_at' => $lane->updatedAt, + 'updated_by' => $lane->updatedBy, ] ); @@ -62,6 +64,21 @@ class SwimLaneRepository extends Repository ); } + public function updateCardAgeSettings(int $id, bool $showCardAge, int $cardAgeWarningDays, string $updatedAt, string $updatedBy): void + { + $this->database->execute( + 'UPDATE swim_lanes SET show_card_age = :show_card_age, card_age_warning_days = :card_age_warning_days, + updated_at = :updated_at, updated_by = :updated_by WHERE id = :id', + [ + 'show_card_age' => $showCardAge ? 1 : 0, + 'card_age_warning_days' => $cardAgeWarningDays, + 'updated_at' => $updatedAt, + 'updated_by' => $updatedBy, + 'id' => $id, + ] + ); + } + public function updatePosition(int $id, int $position, string $updatedAt, string $updatedBy): void { $this->database->execute( diff --git a/app/Views/boards/show.php b/app/Views/boards/show.php index 766c423..156b8c3 100644 --- a/app/Views/boards/show.php +++ b/app/Views/boards/show.php @@ -84,12 +84,10 @@ var KANBAN = { boardId: id ?>, boardSlug: "slug) ?>", - cards: , - showCardAge: showCardAge ? 'true' : 'false' ?>, - cardAgeWarningDays: cardAgeWarningDays ?> + cards: }; -var KANBAN_COLS = ['id' => $c->id, 'name' => $c->name, 'position' => $c->position, 'show_card_count' => $c->showCardCount], $columns)) ?>; -var KANBAN_LANES = ['id' => $l->id, 'name' => $l->name, 'position' => $l->position], $lanes)) ?>; +var KANBAN_COLS = ['id' => $c->id, 'name' => $c->name, 'position' => $c->position, 'show_card_count' => $c->showCardCount, 'show_card_age' => $c->showCardAge, 'card_age_warning_days' => $c->cardAgeWarningDays], $columns)) ?>; +var KANBAN_LANES = ['id' => $l->id, 'name' => $l->name, 'position' => $l->position, 'show_card_age' => $l->showCardAge, 'card_age_warning_days' => $l->cardAgeWarningDays], $lanes)) ?>; diff --git a/app/Views/partials/settings-panel.php b/app/Views/partials/settings-panel.php index 4bd5bc0..bf6a47e 100644 --- a/app/Views/partials/settings-panel.php +++ b/app/Views/partials/settings-panel.php @@ -28,20 +28,41 @@ @@ -64,43 +85,41 @@ - -
- Card Age - -
- showCardAge ? 'checked' : '' ?>> - -
- - -
- - days -
-
Set to 0 to disable.
-
- diff --git a/database/migrations/20260615_000004_add_card_age_settings_to_board_columns.php b/database/migrations/20260615_000004_add_card_age_settings_to_board_columns.php new file mode 100644 index 0000000..7cb5ebb --- /dev/null +++ b/database/migrations/20260615_000004_add_card_age_settings_to_board_columns.php @@ -0,0 +1,24 @@ +execute( + 'ALTER TABLE board_columns ADD COLUMN show_card_age INTEGER NOT NULL DEFAULT 0' + ); + $database->execute( + 'ALTER TABLE board_columns ADD COLUMN card_age_warning_days INTEGER NOT NULL DEFAULT 0' + ); + } + + public function down(Database $database): void + { + // SQLite cannot drop columns without recreating the table. + } +}; diff --git a/database/migrations/20260615_000005_add_card_age_settings_to_swim_lanes.php b/database/migrations/20260615_000005_add_card_age_settings_to_swim_lanes.php new file mode 100644 index 0000000..62708d3 --- /dev/null +++ b/database/migrations/20260615_000005_add_card_age_settings_to_swim_lanes.php @@ -0,0 +1,24 @@ +execute( + 'ALTER TABLE swim_lanes ADD COLUMN show_card_age INTEGER NOT NULL DEFAULT 0' + ); + $database->execute( + 'ALTER TABLE swim_lanes ADD COLUMN card_age_warning_days INTEGER NOT NULL DEFAULT 0' + ); + } + + public function down(Database $database): void + { + // SQLite cannot drop columns without recreating the table. + } +}; diff --git a/database/migrations/20260615_000006_remove_card_age_settings_from_boards.php b/database/migrations/20260615_000006_remove_card_age_settings_from_boards.php new file mode 100644 index 0000000..60bac69 --- /dev/null +++ b/database/migrations/20260615_000006_remove_card_age_settings_from_boards.php @@ -0,0 +1,25 @@ +execute('ALTER TABLE boards DROP COLUMN show_card_age'); + $database->execute('ALTER TABLE boards DROP COLUMN card_age_warning_days'); + } + + public function down(Database $database): void + { + $database->execute('ALTER TABLE boards ADD COLUMN show_card_age INTEGER NOT NULL DEFAULT 0'); + $database->execute('ALTER TABLE boards ADD COLUMN card_age_warning_days INTEGER NOT NULL DEFAULT 0'); + } +}; diff --git a/public/js/kanban-board.js b/public/js/kanban-board.js index 8c62ab1..833a1e2 100644 --- a/public/js/kanban-board.js +++ b/public/js/kanban-board.js @@ -6,6 +6,8 @@ var laneCollapseStorageKey = 'kanban_lane_collapsed_' + String(boardId); var collapsedLaneIds = loadCollapsedLaneIds(); var columnShowCount = {}; + var columnAgeSettings = {}; + var laneAgeSettings = {}; var searchState = { query: '' }; @@ -69,8 +71,26 @@ return 'less than an hour'; } + function effectiveCardAgeSettings(card) { + var colSettings = columnAgeSettings[String(card.column_id)] || { showCardAge: false, cardAgeWarningDays: 0 }; + var laneSettings = laneAgeSettings[String(card.swim_lane_id)] || { showCardAge: false, cardAgeWarningDays: 0 }; + + var showCardAge = colSettings.showCardAge || laneSettings.showCardAge; + + var warningDays = 0; + if (colSettings.showCardAge) { + warningDays = colSettings.cardAgeWarningDays; + } else if (laneSettings.showCardAge) { + warningDays = laneSettings.cardAgeWarningDays; + } + + return { showCardAge: showCardAge, cardAgeWarningDays: warningDays }; + } + function applyCardAge(el, card) { - if (KANBAN.showCardAge) { + var settings = effectiveCardAgeSettings(card); + + if (settings.showCardAge) { var age = formatAge(card.cell_entered_at); if (age) { el.title = 'In this column/lane for ' + age; @@ -81,8 +101,8 @@ el.removeAttribute('title'); } - var isOverdue = KANBAN.cardAgeWarningDays > 0 && - ageInDays(card.cell_entered_at) >= KANBAN.cardAgeWarningDays; + var isOverdue = settings.cardAgeWarningDays > 0 && + ageInDays(card.cell_entered_at) >= settings.cardAgeWarningDays; el.classList.toggle('kanban-card-overdue', isOverdue); } @@ -132,6 +152,21 @@ }); } + function initCardAgeSettings() { + KANBAN_COLS.forEach(function (col) { + columnAgeSettings[String(col.id)] = { + showCardAge: !!col.show_card_age, + cardAgeWarningDays: parseInt(col.card_age_warning_days, 10) || 0 + }; + }); + KANBAN_LANES.forEach(function (lane) { + laneAgeSettings[String(lane.id)] = { + showCardAge: !!lane.show_card_age, + cardAgeWarningDays: parseInt(lane.card_age_warning_days, 10) || 0 + }; + }); + } + function loadCollapsedLaneIds() { var laneMap = {}; try { @@ -470,6 +505,7 @@ grid.insertBefore(hdr, refNode); columnShowCount[String(col.id)] = false; + columnAgeSettings[String(col.id)] = { showCardAge: false, cardAgeWarningDays: 0 }; var laneHeaders = grid.querySelectorAll('.kanban-lane-header'); laneHeaders.forEach(function (lh) { @@ -490,6 +526,7 @@ document.querySelectorAll('.kanban-cell[data-col-id="' + colId + '"]').forEach(function (el) { el.remove(); }); KANBAN.cards = KANBAN.cards.filter(function (c) { return String(c.column_id) !== String(colId); }); delete columnShowCount[String(colId)]; + delete columnAgeSettings[String(colId)]; applyGridTemplate(); }, addLane: function (lane) { @@ -507,6 +544,8 @@ grid.appendChild(lh); bindLaneHeaderToggle(lh); + laneAgeSettings[String(lane.id)] = { showCardAge: false, cardAgeWarningDays: 0 }; + colHeaders.forEach(function (ch) { var cell = document.createElement('div'); cell.className = 'kanban-cell'; @@ -526,6 +565,7 @@ document.querySelector('.kanban-lane-header[data-lane-id="' + laneId + '"]').remove(); document.querySelectorAll('.kanban-cell[data-lane-id="' + laneId + '"]').forEach(function (el) { el.remove(); }); KANBAN.cards = KANBAN.cards.filter(function (c) { return String(c.swim_lane_id) !== String(laneId); }); + delete laneAgeSettings[String(laneId)]; if (collapsedLaneIds[String(laneId)]) { delete collapsedLaneIds[String(laneId)]; saveCollapsedLaneIds(); @@ -543,14 +583,24 @@ columnShowCount[String(colId)] = show; refreshColumnCount(colId); }, - setCardAgeSettings: function (showCardAge, cardAgeWarningDays) { - KANBAN.showCardAge = !!showCardAge; - KANBAN.cardAgeWarningDays = parseInt(cardAgeWarningDays, 10) || 0; + setColumnCardAge: function (colId, showCardAge, cardAgeWarningDays) { + columnAgeSettings[String(colId)] = { + showCardAge: !!showCardAge, + cardAgeWarningDays: parseInt(cardAgeWarningDays, 10) || 0 + }; + refreshAllCardAges(); + }, + setLaneCardAge: function (laneId, showCardAge, cardAgeWarningDays) { + laneAgeSettings[String(laneId)] = { + showCardAge: !!showCardAge, + cardAgeWarningDays: parseInt(cardAgeWarningDays, 10) || 0 + }; refreshAllCardAges(); } }; applyGridTemplate(); + initCardAgeSettings(); renderCards(); initSortables(); initJobSearch(); diff --git a/public/js/kanban-settings.js b/public/js/kanban-settings.js index d3a1344..0a13798 100644 --- a/public/js/kanban-settings.js +++ b/public/js/kanban-settings.js @@ -45,22 +45,72 @@ }); } - function buildListItem(id, name, editClass, deleteClass, labelClass, countToggle) { + function buildListItem(id, name, editClass, deleteClass, labelClass, countToggle, agePrefix) { var li = document.createElement('li'); - li.className = 'list-group-item d-flex align-items-center gap-2 py-2'; + li.className = 'list-group-item py-2'; li.dataset.id = id; li.innerHTML = - '' + - '' + esc(name) + '' + - (countToggle ? - '
' + - '' + - '
' : '') + - '' + - ''; + '
' + + '' + + '' + esc(name) + '' + + (countToggle ? + '
' + + '' + + '
' : '') + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + 'days' + + '
' + + '
Overdue after this many days. 0 = no overdue marking.
' + + '
'; return li; } + /* ── Card age settings (per column / per swim lane) ────────── */ + function bindAgeSettings(li, prefix, urlBase, setter) { + var toggleBtn = li.querySelector('.btn-toggle-' + prefix + '-age'); + var settingsDiv = li.querySelector('.' + prefix + '-age-settings'); + if (toggleBtn && settingsDiv) { + toggleBtn.addEventListener('click', function () { + settingsDiv.classList.toggle('d-none'); + }); + } + + var ageToggle = li.querySelector('.' + prefix + '-age-toggle'); + var ageDays = li.querySelector('.' + prefix + '-age-days'); + if (!ageToggle || !ageDays) return; + + function save() { + var show = ageToggle.checked; + var days = parseInt(ageDays.value, 10); + if (isNaN(days) || days < 0) days = 0; + ageDays.value = days; + + post(urlBase + li.dataset.id + '/card-age-settings', { + show_card_age: show ? '1' : '0', + card_age_warning_days: days + }, function (res) { + if (!res.ok) { + alert(res.error || 'Update failed'); + return; + } + setter(li.dataset.id, res.show_card_age, res.card_age_warning_days); + }); + } + + ageToggle.addEventListener('change', save); + ageDays.addEventListener('change', save); + } + function esc(s) { return String(s) .replace(/&/g, '&').replace(/get('/board/{slug}', [BoardsController::class, 'show']); $router->get('/board/{slug}/edit', [BoardsController::class, 'edit']); $router->post('/board/{slug}/update', [BoardsController::class, 'update']); $router->post('/board/{slug}/delete', [BoardsController::class, 'destroy']); -$router->post('/boards/{id}/card-age-settings', [BoardsController::class, 'updateCardAgeSettings']); // Cards (JSON API) $router->post('/cards', [CardsController::class, 'store']); @@ -32,12 +31,14 @@ $router->post('/cards/{id}', [CardsController::class, 'update']); // Columns (JSON API) — /columns/reorder MUST be before /columns/{id} $router->post('/columns/reorder', [ColumnsController::class, 'reorder']); $router->post('/columns/{id}/toggle-count', [ColumnsController::class, 'toggleCount']); +$router->post('/columns/{id}/card-age-settings', [ColumnsController::class, 'updateCardAgeSettings']); $router->post('/columns/{id}/delete', [ColumnsController::class, 'destroy']); $router->post('/columns/{id}', [ColumnsController::class, 'update']); $router->post('/columns', [ColumnsController::class, 'store']); // Swim lanes (JSON API) — /swimlanes/reorder MUST be before /swimlanes/{id} $router->post('/swimlanes/reorder', [SwimLanesController::class, 'reorder']); +$router->post('/swimlanes/{id}/card-age-settings', [SwimLanesController::class, 'updateCardAgeSettings']); $router->post('/swimlanes/{id}/delete', [SwimLanesController::class, 'destroy']); $router->post('/swimlanes/{id}', [SwimLanesController::class, 'update']); $router->post('/swimlanes', [SwimLanesController::class, 'store']);