@@ -19,12 +19,10 @@ struct DataBrowserView: View {
1919 @State private var columnDetails : [ ColumnInfo ] = [ ]
2020 @State private var rows : [ [ String ? ] ] = [ ]
2121 @State private var isLoading = true
22- @State private var isLoadingMore = false
2322 @State private var appError : AppError ?
2423 @State private var toastMessage : String ?
2524 @State private var toastTask : Task < Void , Never > ?
2625 @State private var pagination = PaginationState ( pageSize: 100 , currentPage: 0 )
27- @State private var hasMore = true
2826 @State private var showInsertSheet = false
2927 @State private var deleteTarget : [ ( column: String , value: String ) ] ?
3028 @State private var showDeleteConfirmation = false
@@ -41,6 +39,16 @@ struct DataBrowserView: View {
4139 columnDetails. contains { $0. isPrimaryKey }
4240 }
4341
42+ private var paginationLabel : String {
43+ guard !rows. isEmpty else { return " " }
44+ let start = pagination. currentOffset + 1
45+ let end = pagination. currentOffset + rows. count
46+ if let total = pagination. totalRows {
47+ return " \( start) – \( end) of \( total) "
48+ }
49+ return " \( start) – \( end) "
50+ }
51+
4452 var body : some View {
4553 Group {
4654 if isLoading {
@@ -69,7 +77,7 @@ struct DataBrowserView: View {
6977 . navigationBarTitleDisplayMode ( . inline)
7078 . toolbar {
7179 ToolbarItem ( placement: . status) {
72- Text ( verbatim: " \( rows . count ) rows " )
80+ Text ( verbatim: paginationLabel )
7381 . font ( . caption)
7482 . foregroundStyle ( . secondary)
7583 }
@@ -93,6 +101,29 @@ struct DataBrowserView: View {
93101 }
94102 }
95103 }
104+ ToolbarItemGroup ( placement: . bottomBar) {
105+ Button {
106+ Task { await goToPreviousPage ( ) }
107+ } label: {
108+ Image ( systemName: " chevron.left " )
109+ }
110+ . disabled ( pagination. currentPage == 0 || isLoading)
111+
112+ Spacer ( )
113+
114+ Text ( paginationLabel)
115+ . font ( . caption)
116+ . foregroundStyle ( . secondary)
117+
118+ Spacer ( )
119+
120+ Button {
121+ Task { await goToNextPage ( ) }
122+ } label: {
123+ Image ( systemName: " chevron.right " )
124+ }
125+ . disabled ( !pagination. hasNextPage || isLoading)
126+ }
96127 }
97128 . task { await loadData ( isInitial: true ) }
98129 . sheet ( isPresented: $showInsertSheet) {
@@ -147,7 +178,6 @@ struct DataBrowserView: View {
147178
148179 private var cardList : some View {
149180 List {
150- // Offset-based identity is acceptable here: rows don't animate/reorder
151181 ForEach ( Array ( rows. enumerated ( ) ) , id: \. offset) { index, row in
152182 NavigationLink {
153183 RowDetailView (
@@ -180,28 +210,6 @@ struct DataBrowserView: View {
180210 }
181211 }
182212 }
183-
184- if hasMore {
185- Section {
186- Button {
187- Task { await loadNextPage ( ) }
188- } label: {
189- HStack {
190- Spacer ( )
191- if isLoadingMore {
192- ProgressView ( )
193- . controlSize ( . small)
194- Text ( " Loading... " )
195- } else {
196- Label ( " Load More " , systemImage: " arrow.down.circle " )
197- }
198- Spacer ( )
199- }
200- . foregroundStyle ( . blue)
201- }
202- . disabled ( isLoadingMore)
203- }
204- }
205213 }
206214 . listStyle ( . plain)
207215 . refreshable { await loadData ( ) }
@@ -224,7 +232,6 @@ struct DataBrowserView: View {
224232 isLoading = true
225233 }
226234 appError = nil
227- pagination. reset ( )
228235
229236 do {
230237 let query = SQLBuilder . buildSelect (
@@ -234,12 +241,13 @@ struct DataBrowserView: View {
234241 let result = try await session. driver. execute ( query: query)
235242 self . columns = result. columns
236243 self . rows = result. rows
237- self . hasMore = result. rows. count >= pagination. pageSize
238244
239245 // columnDetails (from fetchColumns) provides PK info for edit/delete.
240246 // columns (from query result) only have name/type, no PK metadata.
241247 self . columnDetails = try await session. driver. fetchColumns ( table: table. name, schema: nil )
242248
249+ await fetchTotalRows ( session: session)
250+
243251 isLoading = false
244252 } catch {
245253 let context = ErrorContext (
@@ -252,27 +260,27 @@ struct DataBrowserView: View {
252260 }
253261 }
254262
255- private func loadNextPage( ) async {
256- guard let session else { return }
257-
258- isLoadingMore = true
259- pagination. currentPage += 1
260-
263+ private func fetchTotalRows( session: ConnectionSession ) async {
261264 do {
262- let query = SQLBuilder . buildSelect (
263- table: table. name, type: connection. type,
264- limit: pagination. pageSize, offset: pagination. currentOffset
265- )
266- let result = try await session. driver. execute ( query: query)
267- rows. append ( contentsOf: result. rows)
268- hasMore = result. rows. count >= pagination. pageSize
265+ let countQuery = SQLBuilder . buildCount ( table: table. name, type: connection. type)
266+ let countResult = try await session. driver. execute ( query: countQuery)
267+ if let firstRow = countResult. rows. first, let firstCol = firstRow. first {
268+ pagination. totalRows = Int ( firstCol ?? " 0 " )
269+ }
269270 } catch {
270- pagination. currentPage -= 1
271- Self . logger. warning ( " Failed to load next page: \( error. localizedDescription, privacy: . public) " )
272- withAnimation { toastMessage = String ( localized: " Failed to load more rows " ) }
271+ Self . logger. warning ( " Failed to fetch row count: \( error. localizedDescription, privacy: . public) " )
273272 }
273+ }
274+
275+ private func goToNextPage( ) async {
276+ pagination. currentPage += 1
277+ await loadData ( )
278+ }
274279
275- isLoadingMore = false
280+ private func goToPreviousPage( ) async {
281+ guard pagination. currentPage > 0 else { return }
282+ pagination. currentPage -= 1
283+ await loadData ( )
276284 }
277285
278286 private func deleteRow( withPKs pkValues: [ ( column: String , value: String ) ] ) async {
0 commit comments