Add "go to last page" and page count to notifications and devices lists

The list endpoints now return a total count alongside the page so the
front-end can show how many pages exist and offer a last-page jump.

Backend: add count(is_new) and count_devices(...) (sharing a WHERE-builder
with list_devices so page and count can't drift), wrap both list responses
in {items, total_count} structs, and register them with utoipa.

Front-end: parse the wrapper shape (dropping the fetch-one-extra trick),
add a Last-page button and a responsive "Page X of Y" / "X / Y" label to
the shared PaginationBar, and track the total in both lists. Notifications
re-sync the count on every fetch and decrement it locally on mark-read/
unread removals so the count stays accurate without a re-fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-05 21:09:25 -04:00
co-authored by Claude Opus 4.8
parent 3cb104e364
commit 844d7d189c
21 changed files with 765 additions and 275 deletions
+7 -4
View File
@@ -46,7 +46,9 @@ class _DeviceListState extends State<DeviceList> with RouteAware {
bool _sortAscending = false;
int _currentPage = 0;
bool _hasNextPage = false;
// Total devices matching the current filters, used to show how many pages
// exist and to offer "go to last page".
int _totalCount = 0;
bool _didInitialFetch = false;
CancelToken? _fetchToken;
final ScrollController _scrollController = ScrollController();
@@ -54,6 +56,7 @@ class _DeviceListState extends State<DeviceList> with RouteAware {
int get _pageSize => MediaQuery.sizeOf(context).width < Breakpoints.medium
? _phonePageSize
: _widePageSize;
int get _totalPages => (_totalCount / _pageSize).ceil().clamp(1, 1 << 30);
@override
void initState() {
@@ -145,7 +148,7 @@ class _DeviceListState extends State<DeviceList> with RouteAware {
if (!mounted || token != _fetchToken) return;
setState(() {
_currentPage = page;
_hasNextPage = result.hasNextPage;
_totalCount = result.totalCount;
_devices = result.items;
_isLoading = false;
_isPaging = false;
@@ -349,11 +352,11 @@ class _DeviceListState extends State<DeviceList> with RouteAware {
separatorBuilder: (_, _) =>
isWide ? const Divider(height: 1) : const SizedBox.shrink(),
),
if (_currentPage > 0 || _hasNextPage)
if (_currentPage > 0 || _totalPages > 1)
SliverToBoxAdapter(
child: PaginationBar(
currentPage: _currentPage,
hasNextPage: _hasNextPage,
totalPages: _totalPages,
isLoading: _isPaging,
onPageChanged: (page) =>
_fetchPage(page, scrollToTop: true, paging: true),