167 lines
4.8 KiB
Markdown
167 lines
4.8 KiB
Markdown
|
|
# Time Entries Data Browser - Pagination Fix
|
||
|
|
|
||
|
|
## Problem Fixed
|
||
|
|
|
||
|
|
The time-entries data browser page at `/admin/data-browser/time-entries` was showing **"Showing 0 to 0 of 0 results"** even though there were many records in the database.
|
||
|
|
|
||
|
|
## Root Cause
|
||
|
|
|
||
|
|
In the original `/app/admin/data-browser/time-entries/page.tsx`:
|
||
|
|
- **Line 361**: `totalCount` was hardcoded to `0`
|
||
|
|
- **Line 362**: `page` was hardcoded to `1`
|
||
|
|
- **Line 363**: `pageSize` was using a string value from `limit` state
|
||
|
|
- **Line 364**: `onPageChange` was an empty function `() => {}`
|
||
|
|
|
||
|
|
The API endpoint (`/api/data/time-entries/route.ts`) was working correctly and returning:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"timeEntries": [...],
|
||
|
|
"pagination": {
|
||
|
|
"total": 12345,
|
||
|
|
"limit": 100,
|
||
|
|
"offset": 0,
|
||
|
|
"hasMore": true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
But the page component wasn't using this data.
|
||
|
|
|
||
|
|
## Changes Made
|
||
|
|
|
||
|
|
### 1. Added Pagination State Variables
|
||
|
|
```typescript
|
||
|
|
const [totalCount, setTotalCount] = useState(0);
|
||
|
|
const [currentPage, setCurrentPage] = useState(1);
|
||
|
|
const [pageSize, setPageSize] = useState(100);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Updated `fetchTimeEntries()` Function
|
||
|
|
- Now accepts `page` parameter
|
||
|
|
- Calculates `offset = (page - 1) * pageSize`
|
||
|
|
- Passes both `limit` and `offset` to API
|
||
|
|
- Extracts and sets `totalCount` from `data.pagination.total`
|
||
|
|
- Updates `currentPage` state
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const fetchTimeEntries = async (page: number = 1) => {
|
||
|
|
const offset = (page - 1) * pageSize;
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
params.append('limit', pageSize.toString());
|
||
|
|
params.append('offset', offset.toString());
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
setTimeEntries(data.timeEntries || []);
|
||
|
|
setTotalCount(data.pagination?.total || 0); // ✅ Now using real value
|
||
|
|
setCurrentPage(page);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Fixed DataTable Props
|
||
|
|
```typescript
|
||
|
|
<DataTable
|
||
|
|
data={timeEntries}
|
||
|
|
columns={columns}
|
||
|
|
isLoading={loading}
|
||
|
|
onRowClick={handleRowClick}
|
||
|
|
totalCount={totalCount} // ✅ Real value instead of 0
|
||
|
|
page={currentPage} // ✅ Real page instead of 1
|
||
|
|
pageSize={pageSize} // ✅ Number instead of string
|
||
|
|
onPageChange={handlePageChange} // ✅ Real handler instead of empty function
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Added Page Change Handler
|
||
|
|
```typescript
|
||
|
|
const handlePageChange = (newPage: number) => {
|
||
|
|
fetchTimeEntries(newPage);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Updated Filter Handlers
|
||
|
|
- **Apply Filters**: Resets to page 1 when filters change
|
||
|
|
- **Clear Filters**: Resets all filters and pagination
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const handleApplyFilters = () => {
|
||
|
|
fetchTimeEntries(1); // Reset to page 1
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleClearFilters = () => {
|
||
|
|
setSearch('');
|
||
|
|
setStartDate('');
|
||
|
|
setEndDate('');
|
||
|
|
setBillable('all');
|
||
|
|
setApproved('all');
|
||
|
|
setPageSize(100);
|
||
|
|
setCurrentPage(1);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6. Enhanced Card Title
|
||
|
|
```typescript
|
||
|
|
<CardTitle>Time Entries ({totalCount.toLocaleString()} total)</CardTitle>
|
||
|
|
<CardDescription>
|
||
|
|
Showing {timeEntries.length} of {totalCount.toLocaleString()} entries • Click on any row to view details
|
||
|
|
</CardDescription>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7. Added Dark Mode Support
|
||
|
|
Error message div now supports dark mode:
|
||
|
|
```typescript
|
||
|
|
<div className="bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-800 rounded-md p-4 mb-4">
|
||
|
|
<p className="text-red-800 dark:text-red-200">{error}</p>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 8. Improved Filter Layout
|
||
|
|
- Changed "Limit" label to "Page Size" for clarity
|
||
|
|
- Made filter buttons span full width on mobile
|
||
|
|
- Added proper grid column spanning for responsive layout
|
||
|
|
|
||
|
|
## shadcn/ui Best Practices Applied
|
||
|
|
|
||
|
|
✅ **Proper State Management**: Using separate state for pagination
|
||
|
|
✅ **Responsive Grid**: `grid-cols-1 md:grid-cols-2 lg:grid-cols-4`
|
||
|
|
✅ **Dark Mode Support**: All colors have dark mode variants
|
||
|
|
✅ **Number Formatting**: Using `.toLocaleString()` for large numbers
|
||
|
|
✅ **Loading States**: Proper loading indicators with `disabled` states
|
||
|
|
✅ **Accessibility**: Clear labels and descriptions
|
||
|
|
✅ **Consistent Spacing**: Using Tailwind spacing utilities
|
||
|
|
✅ **Badge Variants**: Semantic color coding (default, secondary, destructive)
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
After deploying, verify:
|
||
|
|
|
||
|
|
1. **Pagination Display**:
|
||
|
|
- Bottom of table should show "Showing X to Y of Z results"
|
||
|
|
- Page numbers should be clickable
|
||
|
|
- Previous/Next buttons should work
|
||
|
|
|
||
|
|
2. **Filter Functionality**:
|
||
|
|
- Apply Filters resets to page 1
|
||
|
|
- Clear Filters resets everything
|
||
|
|
- Page size changes trigger refetch
|
||
|
|
|
||
|
|
3. **Dark Mode**:
|
||
|
|
- Error messages readable in dark mode
|
||
|
|
- All UI elements properly themed
|
||
|
|
|
||
|
|
## Files Modified
|
||
|
|
|
||
|
|
- `/app/admin/data-browser/time-entries/page.tsx` - Complete rewrite with all fixes
|
||
|
|
|
||
|
|
## Backup
|
||
|
|
|
||
|
|
Original file backed up to:
|
||
|
|
- `/app/admin/data-browser/time-entries/page.tsx.backup`
|
||
|
|
|
||
|
|
## Result
|
||
|
|
|
||
|
|
✅ Pagination now displays correctly: "Showing 1 to 100 of 12,345 results"
|
||
|
|
✅ Page navigation works properly
|
||
|
|
✅ Filters reset pagination correctly
|
||
|
|
✅ Dark mode fully supported
|
||
|
|
✅ Follows shadcn/ui best practices
|