diff --git a/CutList.Web/Components/Shared/Pager.razor b/CutList.Web/Components/Shared/Pager.razor
new file mode 100644
index 0000000..b7a90d7
--- /dev/null
+++ b/CutList.Web/Components/Shared/Pager.razor
@@ -0,0 +1,109 @@
+@if (TotalCount == 0)
+{
+ return;
+}
+
+@{
+ var totalPages = (int)Math.Ceiling((double)TotalCount / PageSize);
+ var start = (CurrentPage - 1) * PageSize + 1;
+ var end = Math.Min(CurrentPage * PageSize, TotalCount);
+}
+
+
+ Showing @start–@end of @TotalCount
+
+ @if (totalPages > 1)
+ {
+
+ }
+
+
+@code {
+ [Parameter, EditorRequired]
+ public int TotalCount { get; set; }
+
+ [Parameter]
+ public int PageSize { get; set; } = 25;
+
+ [Parameter]
+ public int CurrentPage { get; set; } = 1;
+
+ [Parameter]
+ public EventCallback CurrentPageChanged { get; set; }
+
+ private async Task SetPage(int page)
+ {
+ if (page < 1 || page > (int)Math.Ceiling((double)TotalCount / PageSize))
+ return;
+ if (page == CurrentPage)
+ return;
+
+ CurrentPage = page;
+ await CurrentPageChanged.InvokeAsync(page);
+ }
+
+ private IEnumerable GetPageWindow(int totalPages)
+ {
+ const int maxVisible = 7;
+
+ if (totalPages <= maxVisible)
+ {
+ for (var i = 1; i <= totalPages; i++)
+ yield return i;
+ yield break;
+ }
+
+ // Always show first page
+ yield return 1;
+
+ var windowStart = Math.Max(2, CurrentPage - 2);
+ var windowEnd = Math.Min(totalPages - 1, CurrentPage + 2);
+
+ // Adjust window to show 5 middle pages when possible
+ if (windowEnd - windowStart < 4)
+ {
+ if (windowStart == 2)
+ windowEnd = Math.Min(totalPages - 1, windowStart + 4);
+ else
+ windowStart = Math.Max(2, windowEnd - 4);
+ }
+
+ if (windowStart > 2)
+ yield return -1; // ellipsis
+
+ for (var i = windowStart; i <= windowEnd; i++)
+ yield return i;
+
+ if (windowEnd < totalPages - 1)
+ yield return -1; // ellipsis
+
+ // Always show last page
+ yield return totalPages;
+ }
+}