From eda3d52a760b3cc3d7dc8822cbaad07c382a238d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Jan 2026 23:08:55 +0000 Subject: [PATCH 1/5] Add pagination to Issues and PRs list view Implements 100 items per page pagination for the IssuesAndPRsList component, consistent with the EventView pagination implementation. Features include: - Page state persisted in localStorage - Reset to page 1 when search text changes - Page info displayed in header (Page X of Y) - Pagination controls at bottom of list --- src/views/IssuesAndPRsList.tsx | 53 +++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/views/IssuesAndPRsList.tsx b/src/views/IssuesAndPRsList.tsx index 6d01e57..958ebbe 100644 --- a/src/views/IssuesAndPRsList.tsx +++ b/src/views/IssuesAndPRsList.tsx @@ -1,10 +1,11 @@ -import React, { memo, useState, useMemo, useCallback } from 'react'; +import React, { memo, useState, useMemo, useCallback, useEffect } from 'react'; import { Box, Text, Checkbox, Button, Heading, + Pagination, } from '@primer/react'; import { GitPullRequestIcon, @@ -95,6 +96,10 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ const [selectedItems, setSelectedItems] = useLocalStorage>('issuesAndPRs-selectedItems', new Set()); const [collapsedSections, setCollapsedSections] = useLocalStorage>('issuesAndPRs-collapsedSections', new Set()); + // Pagination state + const [currentPage, setCurrentPage] = useLocalStorage('issuesAndPRs-currentPage', 1); + const itemsPerPage = 100; + // Use copy feedback hook const { isCopied, triggerCopy } = useCopyFeedback(2000); @@ -103,11 +108,27 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ return filterItemsByAdvancedSearch(results, searchText); }, [results, searchText]); + // Reset to first page when search changes + useEffect(() => { + setCurrentPage(1); + }, [searchText]); + + // Calculate pagination + const totalPages = Math.ceil(filteredResults.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const paginatedResults = filteredResults.slice(startIndex, endIndex); + + // Handle page change + const handlePageChange = useCallback((_event: React.MouseEvent, page: number) => { + setCurrentPage(page); + }, []); + // Define helper variables for empty state logic (consistent with other views) const hasRawData = results && results.length > 0; const hasSearchText = searchText && searchText.trim().length > 0; - // Group items into sections + // Group items into sections (uses paginated results) const groupedItems = useMemo(() => { const groups: { 'PRs': GitHubItem[]; @@ -117,7 +138,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ 'Issues': [], }; - filteredResults.forEach(item => { + paginatedResults.forEach(item => { if (item.pull_request) { // All pull requests go to PRs section groups['PRs'].push(item); @@ -133,7 +154,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ }); return groups; - }, [filteredResults]); + }, [paginatedResults]); // Selection handlers const toggleItemSelection = useCallback((id: string | number) => { @@ -350,6 +371,11 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ }} > Select All + {totalPages > 1 && ( + + (Page {currentPage} of {totalPages}) + + )} ); })} + + {/* Pagination */} + {totalPages > 1 && ( + + + + )} ); })()} From d8f064873285fc5a78f284bcdd821e351fea034f Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Sun, 11 Jan 2026 08:40:30 +0100 Subject: [PATCH 2/5] Filter issues/PRs to only include items where user is author or assignee Adds post-fetch filtering to ensure only items where the searched user is the author or an assignee are included in the results. This filters out items that may be returned by GitHub's search API but don't directly involve the user. Co-Authored-By: Claude Opus 4.5 --- src/hooks/useGitHubDataFetching.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/hooks/useGitHubDataFetching.ts b/src/hooks/useGitHubDataFetching.ts index 54d53bf..bac01d3 100644 --- a/src/hooks/useGitHubDataFetching.ts +++ b/src/hooks/useGitHubDataFetching.ts @@ -235,7 +235,21 @@ export const useGitHubDataFetching = ({ assignees: item.assignees || [], original: item, // Store the original item as the original payload })); - allSearchItems.push(...searchItemsWithOriginal); + + // Filter to only include items where user is author or assignee + const filteredSearchItems = searchItemsWithOriginal.filter((item: Record) => { + const user = item.user as { login: string } | null; + const assignee = item.assignee as { login: string } | null; + const assignees = item.assignees as Array<{ login: string }> | null; + + const isAuthor = user?.login?.toLowerCase() === singleUsername.toLowerCase(); + const isAssignee = assignee?.login?.toLowerCase() === singleUsername.toLowerCase(); + const isInAssignees = assignees?.some(a => a.login?.toLowerCase() === singleUsername.toLowerCase()) ?? false; + + return isAuthor || isAssignee || isInAssignees; + }); + + allSearchItems.push(...filteredSearchItems); onProgress(`Fetched issues/PRs for ${singleUsername}`); // Fetch all events with pagination From c08b090058bfc602c227c5f9fd5490d67687f1c1 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Sun, 11 Jan 2026 08:56:03 +0100 Subject: [PATCH 3/5] Fix double fetch of events on initial load Use a ref to track if the initial fetch has been triggered, preventing the useEffect from firing twice when handleSearch is recreated due to dependency changes (indexedDBEvents.length/indexedDBSearchItems.length). Co-Authored-By: Claude Opus 4.5 --- src/App.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0194638..93d3d1f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,9 @@ import { useState, - useEffect, + useEffect, useCallback, useMemo, + useRef, createContext, useContext, } from 'react'; @@ -168,28 +169,34 @@ function App() { setTimeout(() => setIsManuallySpinning(false), 2000); }, []); + // Track if initial fetch has been triggered to prevent double fetches + const initialFetchTriggeredRef = useRef(false); + useEffect(() => { - if (initialLoadingCount === 1) { + if (initialLoadingCount === 1 && !initialFetchTriggeredRef.current) { + // Mark as triggered immediately to prevent duplicate calls + initialFetchTriggeredRef.current = true; + // Skip initial loading in test environment - const isTestEnvironment = typeof window !== 'undefined' && - (window.navigator?.userAgent?.includes('jsdom') || + const isTestEnvironment = typeof window !== 'undefined' && + (window.navigator?.userAgent?.includes('jsdom') || process.env.NODE_ENV === 'test' || import.meta.env?.MODE === 'test'); - + if (isTestEnvironment) { // In tests, immediately exit loading mode setInitialLoadingCount(0); setIsDataLoadingComplete(false); return; } - + const startTime = Date.now(); const minLoadingTime = 3000; // 3 seconds minimum for better UX - + handleSearch().then(() => { const elapsedTime = Date.now() - startTime; const remainingTime = Math.max(0, minLoadingTime - elapsedTime); - + // Instead of automatically switching, mark data loading as complete setTimeout(() => { setIsDataLoadingComplete(true); @@ -201,6 +208,8 @@ function App() { const handleStartClick = useCallback(() => { setInitialLoadingCount(0); setIsDataLoadingComplete(false); + // Reset the fetch trigger ref so a new fetch can happen + initialFetchTriggeredRef.current = false; }, []); if (initialLoadingCount === 1) { From 78c0d21ef9c0462f38da9ad0826034a5e1dc2bca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Jan 2026 09:22:18 +0000 Subject: [PATCH 4/5] Add separate pagination for PRs and Issues sections Each section now has its own independent pagination with 100 items per page: - PRs section: separate page state stored in localStorage - Issues section: separate page state stored in localStorage - Page info shown in each section header (e.g., "Page 1 of 3") - Pagination controls at bottom of each section - Both sections reset to page 1 when search text changes --- src/views/IssuesAndPRsList.tsx | 328 ++++++++++++++++++++++----------- 1 file changed, 216 insertions(+), 112 deletions(-) diff --git a/src/views/IssuesAndPRsList.tsx b/src/views/IssuesAndPRsList.tsx index 958ebbe..5a5d634 100644 --- a/src/views/IssuesAndPRsList.tsx +++ b/src/views/IssuesAndPRsList.tsx @@ -96,8 +96,9 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ const [selectedItems, setSelectedItems] = useLocalStorage>('issuesAndPRs-selectedItems', new Set()); const [collapsedSections, setCollapsedSections] = useLocalStorage>('issuesAndPRs-collapsedSections', new Set()); - // Pagination state - const [currentPage, setCurrentPage] = useLocalStorage('issuesAndPRs-currentPage', 1); + // Pagination state - separate for each section + const [prsCurrentPage, setPrsCurrentPage] = useLocalStorage('issuesAndPRs-prsCurrentPage', 1); + const [issuesCurrentPage, setIssuesCurrentPage] = useLocalStorage('issuesAndPRs-issuesCurrentPage', 1); const itemsPerPage = 100; // Use copy feedback hook @@ -110,25 +111,15 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ // Reset to first page when search changes useEffect(() => { - setCurrentPage(1); + setPrsCurrentPage(1); + setIssuesCurrentPage(1); }, [searchText]); - // Calculate pagination - const totalPages = Math.ceil(filteredResults.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; - const paginatedResults = filteredResults.slice(startIndex, endIndex); - - // Handle page change - const handlePageChange = useCallback((_event: React.MouseEvent, page: number) => { - setCurrentPage(page); - }, []); - // Define helper variables for empty state logic (consistent with other views) const hasRawData = results && results.length > 0; const hasSearchText = searchText && searchText.trim().length > 0; - // Group items into sections (uses paginated results) + // Group items into sections (before pagination) const groupedItems = useMemo(() => { const groups: { 'PRs': GitHubItem[]; @@ -138,7 +129,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ 'Issues': [], }; - paginatedResults.forEach(item => { + filteredResults.forEach(item => { if (item.pull_request) { // All pull requests go to PRs section groups['PRs'].push(item); @@ -154,7 +145,31 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ }); return groups; - }, [paginatedResults]); + }, [filteredResults]); + + // Calculate pagination for each section + const prsTotalPages = Math.ceil(groupedItems['PRs'].length / itemsPerPage); + const prsStartIndex = (prsCurrentPage - 1) * itemsPerPage; + const paginatedPRs = groupedItems['PRs'].slice(prsStartIndex, prsStartIndex + itemsPerPage); + + const issuesTotalPages = Math.ceil(groupedItems['Issues'].length / itemsPerPage); + const issuesStartIndex = (issuesCurrentPage - 1) * itemsPerPage; + const paginatedIssues = groupedItems['Issues'].slice(issuesStartIndex, issuesStartIndex + itemsPerPage); + + // Paginated grouped items for display + const paginatedGroupedItems = useMemo(() => ({ + 'PRs': paginatedPRs, + 'Issues': paginatedIssues, + }), [paginatedPRs, paginatedIssues]); + + // Handle page change for each section + const handlePrsPageChange = useCallback((_event: React.MouseEvent, page: number) => { + setPrsCurrentPage(page); + }, []); + + const handleIssuesPageChange = useCallback((_event: React.MouseEvent, page: number) => { + setIssuesCurrentPage(page); + }, []); // Selection handlers const toggleItemSelection = useCallback((id: string | number) => { @@ -170,11 +185,11 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ }, []); const selectAllItems = useCallback(() => { - const allDisplayedItems = Object.entries(groupedItems) + const allDisplayedItems = Object.entries(paginatedGroupedItems) .filter(([groupName]) => !collapsedSections.has(groupName)) .flatMap(([, items]) => items); setSelectedItems(new Set(allDisplayedItems.map((item: GitHubItem) => item.event_id || item.id))); - }, [groupedItems, collapsedSections]); + }, [paginatedGroupedItems, collapsedSections]); const clearSelection = useCallback(() => { setSelectedItems(new Set()); @@ -197,16 +212,16 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ setCollapsedSections(prev => { const newSet = new Set(prev); const isCurrentlyCollapsed = newSet.has(sectionName); - + if (isCurrentlyCollapsed) { // Expanding the section newSet.delete(sectionName); } else { // Collapsing the section - clear any selected items in this section newSet.add(sectionName); - + // Find all items in this section and remove them from selection - const sectionItems = groupedItems[sectionName as keyof typeof groupedItems] || []; + const sectionItems = paginatedGroupedItems[sectionName as keyof typeof paginatedGroupedItems] || []; if (sectionItems.length > 0) { setSelectedItems(prevSelected => { const newSelected = new Set(prevSelected); @@ -219,12 +234,12 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ } return newSet; }); - }, [groupedItems, setSelectedItems]); + }, [paginatedGroupedItems, setSelectedItems]); // Copy results to clipboard for content const copyResultsToClipboard = useCallback(async (format: 'detailed' | 'compact') => { // Convert to the format expected by clipboard utility - const groupedData = Object.entries(groupedItems) + const groupedData = Object.entries(paginatedGroupedItems) .filter(([, items]) => items.length > 0) .map(([groupName, items]) => ({ groupName, @@ -244,7 +259,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ } // Get all items for the clipboard (either selected or all) - const allItems = Object.values(groupedItems).flat(); + const allItems = Object.values(paginatedGroupedItems).flat(); const selectedItemsArray = selectedItems.size > 0 ? allItems.filter(item => @@ -263,7 +278,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ console.error('Failed to copy grouped results:', error); }, }); - }, [groupedItems, selectedItems, triggerCopy]); + }, [paginatedGroupedItems, selectedItems, triggerCopy]); // Clipboard feedback helper const isClipboardCopied = useCallback((itemId: string | number) => { @@ -272,10 +287,10 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ // Calculate select all checkbox state (only consider expanded sections) const selectAllState = useMemo(() => { - const allDisplayedItems = Object.entries(groupedItems) + const allDisplayedItems = Object.entries(paginatedGroupedItems) .filter(([groupName]) => !collapsedSections.has(groupName)) .flatMap(([, items]) => items); - + if (allDisplayedItems.length === 0) { return { checked: false, indeterminate: false }; } @@ -291,14 +306,14 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ } else { return { checked: false, indeterminate: true }; } - }, [groupedItems, selectedItems, collapsedSections]); + }, [paginatedGroupedItems, selectedItems, collapsedSections]); // Handle select all checkbox click (only consider expanded sections) const handleSelectAllChange = () => { - const allDisplayedItems = Object.entries(groupedItems) + const allDisplayedItems = Object.entries(paginatedGroupedItems) .filter(([groupName]) => !collapsedSections.has(groupName)) .flatMap(([, items]) => items); - + const selectedCount = allDisplayedItems.filter(item => selectedItems.has(item.event_id || item.id) ).length; @@ -320,7 +335,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ // Dialog navigation handlers const handlePreviousItem = () => { if (!selectedItemForDialog) return; - const allItems = Object.values(groupedItems).flat(); + const allItems = Object.values(paginatedGroupedItems).flat(); const currentIndex = allItems.findIndex( item => item.id === selectedItemForDialog.id ); @@ -331,7 +346,7 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ const handleNextItem = () => { if (!selectedItemForDialog) return; - const allItems = Object.values(groupedItems).flat(); + const allItems = Object.values(paginatedGroupedItems).flat(); const currentIndex = allItems.findIndex( item => item.id === selectedItemForDialog.id ); @@ -342,11 +357,11 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ const getCurrentItemIndex = () => { if (!selectedItemForDialog) return -1; - const allItems = Object.values(groupedItems).flat(); + const allItems = Object.values(paginatedGroupedItems).flat(); return allItems.findIndex(item => item.id === selectedItemForDialog.id); }; - const allDisplayedItems = Object.entries(groupedItems) + const allDisplayedItems = Object.entries(paginatedGroupedItems) .filter(([groupName]) => !collapsedSections.has(groupName)) .flatMap(([, items]) => items); @@ -371,11 +386,6 @@ const IssuesAndPRsList = memo(function IssuesAndPRsList({ }} > Select All - {totalPages > 1 && ( - - (Page {currentPage} of {totalPages}) - - )} - {Object.entries(groupedItems).map(([groupName, groupItems]) => { - if (groupItems.length === 0) return null; - - return ( -
-
- - - { - const sectionItemIds = groupItems.map(item => item.event_id || item.id); - return sectionItemIds.length > 0 && sectionItemIds.every(id => selectedItems.has(id)); - })()} - indeterminate={(() => { - const sectionItemIds = groupItems.map(item => item.event_id || item.id); - const selectedCount = sectionItemIds.filter(id => selectedItems.has(id)).length; - return selectedCount > 0 && selectedCount < sectionItemIds.length; - })()} - onChange={() => { - // Don't allow selection if section is collapsed - if (collapsedSections.has(groupName)) return; - - const sectionItemIds = groupItems.map(item => item.event_id || item.id); - const selectedCount = sectionItemIds.filter(id => selectedItems.has(id)).length; - const allSelected = selectedCount === sectionItemIds.length; - bulkSelectItems(sectionItemIds, !allSelected); - }} - sx={{ flexShrink: 0 }} - aria-label={`Select all items in ${groupName} section`} - disabled={collapsedSections.has(groupName)} + {/* PRs Section */} + {groupedItems['PRs'].length > 0 && ( +
+
+ + + { + const sectionItemIds = paginatedGroupedItems['PRs'].map(item => item.event_id || item.id); + return sectionItemIds.length > 0 && sectionItemIds.every(id => selectedItems.has(id)); + })()} + indeterminate={(() => { + const sectionItemIds = paginatedGroupedItems['PRs'].map(item => item.event_id || item.id); + const selectedCount = sectionItemIds.filter(id => selectedItems.has(id)).length; + return selectedCount > 0 && selectedCount < sectionItemIds.length; + })()} + onChange={() => { + if (collapsedSections.has('PRs')) return; + const sectionItemIds = paginatedGroupedItems['PRs'].map(item => item.event_id || item.id); + const selectedCount = sectionItemIds.filter(id => selectedItems.has(id)).length; + const allSelected = selectedCount === sectionItemIds.length; + bulkSelectItems(sectionItemIds, !allSelected); + }} + sx={{ flexShrink: 0 }} + aria-label="Select all items in PRs section" + disabled={collapsedSections.has('PRs')} + /> + + PRs ({groupedItems['PRs'].length}) + {prsTotalPages > 1 && ( + + Page {prsCurrentPage} of {prsTotalPages} + + )} + + + + +
+ {!collapsedSections.has('PRs') && ( + <> +
+ {paginatedGroupedItems['PRs'].map((item: GitHubItem) => ( +
+ +
+ ))} +
+ {prsTotalPages > 1 && ( + + - - {groupName} ({groupItems.length}) - -
+ )} + + {/* Issues Section */} + {groupedItems['Issues'].length > 0 && ( +
+
+ + + { + const sectionItemIds = paginatedGroupedItems['Issues'].map(item => item.event_id || item.id); + return sectionItemIds.length > 0 && sectionItemIds.every(id => selectedItems.has(id)); + })()} + indeterminate={(() => { + const sectionItemIds = paginatedGroupedItems['Issues'].map(item => item.event_id || item.id); + const selectedCount = sectionItemIds.filter(id => selectedItems.has(id)).length; + return selectedCount > 0 && selectedCount < sectionItemIds.length; + })()} + onChange={() => { + if (collapsedSections.has('Issues')) return; + const sectionItemIds = paginatedGroupedItems['Issues'].map(item => item.event_id || item.id); + const selectedCount = sectionItemIds.filter(id => selectedItems.has(id)).length; + const allSelected = selectedCount === sectionItemIds.length; + bulkSelectItems(sectionItemIds, !allSelected); }} - aria-label={`${collapsedSections.has(groupName) ? 'Show' : 'Hide'} ${groupName} section`} - > - {collapsedSections.has(groupName) ? : } - + sx={{ flexShrink: 0 }} + aria-label="Select all items in Issues section" + disabled={collapsedSections.has('Issues')} + /> + + Issues ({groupedItems['Issues'].length}) + {issuesTotalPages > 1 && ( + + Page {issuesCurrentPage} of {issuesTotalPages} + + )} + -
- {!collapsedSections.has(groupName) && ( + + +
+ {!collapsedSections.has('Issues') && ( + <>
- {groupItems.map((item: GitHubItem) => ( + {paginatedGroupedItems['Issues'].map((item: GitHubItem) => (
))}
- )} -
- ); - })} - - {/* Pagination */} - {totalPages > 1 && ( - - - + {issuesTotalPages > 1 && ( + + + + )} + + )} +
)}
); From c9ed2982f15b5baffc1d18cc404b1c45a5c221aa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Feb 2026 14:26:38 +0000 Subject: [PATCH 5/5] Add per-section pagination to Summary view Adds 100 items per page pagination to each section in the Summary view, consistent with the IssuesAndPRsList pagination approach: - Per-section page state stored in localStorage (summary-sectionPages) - Pagination controls at bottom of each section when needed - Pages reset when search text changes - Paginates URL groups (not raw items) since Summary deduplicates by URL --- src/views/Summary.tsx | 76 ++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/views/Summary.tsx b/src/views/Summary.tsx index 08ce445..43bbb7d 100644 --- a/src/views/Summary.tsx +++ b/src/views/Summary.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo, useCallback } from 'react'; +import React, { memo, useMemo, useCallback, useEffect } from 'react'; import { Text, Button, @@ -6,6 +6,7 @@ import { Checkbox, Box, Token, + Pagination, } from '@primer/react'; import { ChevronDownIcon, @@ -76,6 +77,23 @@ const SummaryView = memo(function SummaryView({ const [collapsedSections, setCollapsedSections] = useLocalStorage>('summary-collapsedSections', new Set()); + // Per-section pagination state + const [sectionPages, setSectionPages] = useLocalStorage>('summary-sectionPages', {}); + const itemsPerPage = 100; + + const getSectionPage = useCallback((sectionName: string) => { + return sectionPages[sectionName] || 1; + }, [sectionPages]); + + const setSectionPage = useCallback((sectionName: string, page: number) => { + setSectionPages(prev => ({ ...prev, [sectionName]: page })); + }, [setSectionPages]); + + // Reset pagination when search changes + useEffect(() => { + setSectionPages({}); + }, [searchText, setSectionPages]); + // Filter and sort items const filteredItems = filterItemsByAdvancedSearch(items, searchText); const sortedItems = sortItemsByUpdatedDate(filteredItems); @@ -248,25 +266,45 @@ const SummaryView = memo(function SummaryView({ - {!isCollapsed && ( -
- {Object.entries(urlGroups).map(([url, items]) => { - const mostRecent = getMostRecent(items); - return ( -
- 1 ? items.length : undefined} + {!isCollapsed && (() => { + const urlGroupEntries = Object.entries(urlGroups); + const sectionPage = getSectionPage(groupName); + const sectionTotalPages = Math.ceil(urlGroupEntries.length / itemsPerPage); + const startIndex = (sectionPage - 1) * itemsPerPage; + const paginatedUrlGroups = urlGroupEntries.slice(startIndex, startIndex + itemsPerPage); + + return ( +
+ {paginatedUrlGroups.map(([url, items]) => { + const mostRecent = getMostRecent(items); + return ( +
+ 1 ? items.length : undefined} + /> +
+ ); + })} + {sectionTotalPages > 1 && ( + + setSectionPage(groupName, page)} + showPages={{ narrow: false }} + marginPageCount={2} + surroundingPageCount={2} /> -
- ); - })} -
- )} + + )} +
+ ); + })()} ); })