Skip to content

Commit a884e48

Browse files
balazstasiCopilot
andcommitted
feat: add pagination to incident timeline
Replace the binary Show more/fewer toggle with proper page-based pagination (4 date groups per page). Navigation uses prev/next arrow buttons with a 'Page X of Y' label in the top-right corner of the panel header, consistent with the existing Uptime History controls. - Swap showAll boolean for currentPage / ITEMS_PER_PAGE logic - Reuse .icon-button styling for prev/next arrows - Add .pagination and .pagination-label CSS - Remove bottom footer toggle — controls live only in panel header Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2692dd8 commit a884e48

3 files changed

Lines changed: 138 additions & 22 deletions

File tree

site/app.js

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,12 +1193,53 @@ const render = async () => {
11931193
});
11941194

11951195
const entries = Array.from(grouped.entries());
1196-
let showAll = false;
1197-
const toggleButtons = Array.from(document.querySelectorAll('[data-toggle-timeline]'));
1196+
const ITEMS_PER_PAGE = 4;
1197+
let currentPage = 0;
1198+
let filteredEntries = entries;
1199+
1200+
const prevButtons = Array.from(document.querySelectorAll('[data-page-prev]'));
1201+
const nextButtons = Array.from(document.querySelectorAll('[data-page-next]'));
1202+
const pageLabels = Array.from(document.querySelectorAll('[data-page-label]'));
1203+
const rangeFromInputs = Array.from(document.querySelectorAll('[data-range-from]'));
1204+
const rangeToInputs = Array.from(document.querySelectorAll('[data-range-to]'));
1205+
const rangeClearButtons = Array.from(document.querySelectorAll('[data-range-clear]'));
1206+
1207+
// Build a lookup from each entry index to its raw Date
1208+
const entryDates = entries.map(([, list]) => incidentStartDate(list[0]));
1209+
1210+
// Set min/max on date inputs from incident range
1211+
const toISO = (d) => d.toISOString().slice(0, 10);
1212+
if (entryDates.length) {
1213+
const newest = entryDates[0];
1214+
const oldest = entryDates[entryDates.length - 1];
1215+
[...rangeFromInputs, ...rangeToInputs].forEach((input) => {
1216+
input.min = toISO(oldest);
1217+
input.max = toISO(newest);
1218+
});
1219+
}
1220+
1221+
const getTotalPages = () => Math.max(1, Math.ceil(filteredEntries.length / ITEMS_PER_PAGE));
1222+
1223+
const updatePaginationControls = () => {
1224+
const totalPages = getTotalPages();
1225+
prevButtons.forEach((btn) => (btn.disabled = currentPage <= 0));
1226+
nextButtons.forEach((btn) => (btn.disabled = currentPage >= totalPages - 1));
1227+
pageLabels.forEach((label) => (label.textContent = `Page ${currentPage + 1} of ${totalPages}`));
1228+
rangeClearButtons.forEach((btn) => (btn.hidden = filteredEntries === entries));
1229+
};
11981230

11991231
const renderTimeline = () => {
12001232
timeline.innerHTML = '';
1201-
const slice = showAll ? entries : entries.slice(0, 8);
1233+
const start = currentPage * ITEMS_PER_PAGE;
1234+
const slice = filteredEntries.slice(start, start + ITEMS_PER_PAGE);
1235+
if (slice.length === 0) {
1236+
const empty = document.createElement('p');
1237+
empty.className = 'muted';
1238+
empty.textContent = 'No incidents in this date range.';
1239+
empty.style.textAlign = 'center';
1240+
empty.style.padding = '24px 0';
1241+
timeline.appendChild(empty);
1242+
}
12021243
slice.forEach(([date, list]) => {
12031244
const group = document.createElement('div');
12041245
group.className = 'incident-group';
@@ -1213,24 +1254,43 @@ const render = async () => {
12131254

12141255
timeline.appendChild(group);
12151256
});
1257+
updatePaginationControls();
12161258
};
12171259

1218-
renderTimeline();
1219-
const updateToggleButtons = () => {
1220-
toggleButtons.forEach((button) => {
1221-
button.textContent = showAll ? 'Show fewer' : 'Show more';
1222-
});
1260+
const goToPage = (page) => {
1261+
const totalPages = getTotalPages();
1262+
currentPage = Math.max(0, Math.min(totalPages - 1, page));
1263+
renderTimeline();
12231264
};
12241265

1225-
updateToggleButtons();
1266+
const applyDateFilter = () => {
1267+
const fromVal = rangeFromInputs[0]?.value;
1268+
const toVal = rangeToInputs[0]?.value;
1269+
if (!fromVal && !toVal) {
1270+
filteredEntries = entries;
1271+
} else {
1272+
const from = fromVal ? new Date(fromVal + 'T00:00:00Z') : new Date(0);
1273+
const to = toVal ? new Date(toVal + 'T23:59:59Z') : new Date();
1274+
filteredEntries = entries.filter(([, list]) => {
1275+
const d = incidentStartDate(list[0]);
1276+
return d >= from && d <= to;
1277+
});
1278+
}
1279+
currentPage = 0;
1280+
renderTimeline();
1281+
};
12261282

1227-
toggleButtons.forEach((button) => {
1228-
button.addEventListener('click', () => {
1229-
showAll = !showAll;
1230-
updateToggleButtons();
1231-
renderTimeline();
1232-
});
1233-
});
1283+
prevButtons.forEach((btn) => btn.addEventListener('click', () => goToPage(currentPage - 1)));
1284+
nextButtons.forEach((btn) => btn.addEventListener('click', () => goToPage(currentPage + 1)));
1285+
rangeFromInputs.forEach((input) => input.addEventListener('change', applyDateFilter));
1286+
rangeToInputs.forEach((input) => input.addEventListener('change', applyDateFilter));
1287+
rangeClearButtons.forEach((btn) => btn.addEventListener('click', () => {
1288+
rangeFromInputs.forEach((i) => (i.value = ''));
1289+
rangeToInputs.forEach((i) => (i.value = ''));
1290+
applyDateFilter();
1291+
}));
1292+
1293+
renderTimeline();
12341294
};
12351295

12361296
const renderIncidentCard = (incident, compact = false) => {

site/index.html

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,15 +154,19 @@ <h3>Uptime history</h3>
154154
<div class="panel-header">
155155
<h3>Incident timeline</h3>
156156
<div class="panel-actions">
157-
<button class="ghost-button" type="button" id="togglePast" data-toggle-timeline>Show more</button>
157+
<div class="pagination" id="paginationTop">
158+
<input class="pagination-date" type="date" data-range-from aria-label="From date" title="From" />
159+
<span class="pagination-range-sep"></span>
160+
<input class="pagination-date" type="date" data-range-to aria-label="To date" title="To" />
161+
<button class="icon-button pagination-clear" type="button" data-range-clear aria-label="Clear date filter" title="Clear filter"></button>
162+
<span class="pagination-separator" aria-hidden="true"></span>
163+
<button class="icon-button" type="button" data-page-prev aria-label="Previous page"></button>
164+
<span class="pagination-label" data-page-label>Page 1 of 1</span>
165+
<button class="icon-button" type="button" data-page-next aria-label="Next page"></button>
166+
</div>
158167
</div>
159168
</div>
160169
<div id="incidentTimeline"></div>
161-
<div class="timeline-footer">
162-
<button class="ghost-button" type="button" id="togglePastBottom" data-toggle-timeline>
163-
Show more
164-
</button>
165-
</div>
166170
</section>
167171

168172
<section class="panel panel-raise about-panel" id="about" data-animate>

site/styles.css

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
--timeline-chip-bg: #21262d;
163163
--component-chip-bg: #11161d;
164164
--icon-button-bg: #161b22;
165+
--date-picker-filter: invert(1) brightness(1.2);
165166
--badge-minor-ink: #e3b341;
166167
--badge-major-ink: #ffa198;
167168
--badge-maintenance-ink: #79c0ff;
@@ -1422,6 +1423,57 @@ main {
14221423
margin-top: 18px;
14231424
}
14241425

1426+
.pagination {
1427+
display: flex;
1428+
align-items: center;
1429+
gap: 8px;
1430+
}
1431+
1432+
.pagination-label {
1433+
font-weight: 600;
1434+
color: var(--muted);
1435+
min-width: 100px;
1436+
text-align: center;
1437+
user-select: none;
1438+
}
1439+
1440+
.pagination-date {
1441+
height: 32px;
1442+
border-radius: 8px;
1443+
border: 1px solid var(--border);
1444+
background: var(--icon-button-bg);
1445+
color: var(--ink);
1446+
padding: 0 10px;
1447+
font-family: inherit;
1448+
font-size: 0.85rem;
1449+
cursor: pointer;
1450+
}
1451+
1452+
.pagination-date::-webkit-calendar-picker-indicator {
1453+
filter: var(--date-picker-filter, none);
1454+
cursor: pointer;
1455+
}
1456+
1457+
.pagination-separator {
1458+
width: 1px;
1459+
height: 20px;
1460+
background: var(--border);
1461+
margin: 0 4px;
1462+
}
1463+
1464+
.pagination-range-sep {
1465+
color: var(--muted);
1466+
font-weight: 600;
1467+
}
1468+
1469+
.pagination-clear {
1470+
font-size: 0.75rem;
1471+
}
1472+
1473+
.pagination-clear[hidden] {
1474+
display: none;
1475+
}
1476+
14251477
.about-panel {
14261478
background: var(--about-panel-bg);
14271479
}

0 commit comments

Comments
 (0)