Skip to content

Commit bb9c70a

Browse files
committed
feat: add error states to activity log and bin list pages
Surface API errors in ActivityPage and BinListPage with AlertTriangle empty states. Expose error from LocationProvider context.
1 parent 34a49a9 commit bb9c70a

5 files changed

Lines changed: 24 additions & 10 deletions

File tree

src/features/activity/ActivityPage.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Clock } from 'lucide-react';
1+
import { AlertTriangle, Clock } from 'lucide-react';
22
import { useEffect, useMemo, useState } from 'react';
33
import { useNavigate } from 'react-router-dom';
44
import { Crossfade } from '@/components/ui/crossfade';
@@ -22,7 +22,7 @@ export function ActivityPage() {
2222
() => ENTITY_TYPE_FILTERS.map((f) => ({ key: f.value, label: f.tKey ? t[f.tKey] : f.label })),
2323
[t],
2424
);
25-
const { entries, isLoading, isLoadingMore, hasMore, loadMore } = usePaginatedActivityLog(entityTypeFilter, 50);
25+
const { entries, isLoading, isLoadingMore, hasMore, error, loadMore } = usePaginatedActivityLog(entityTypeFilter, 50);
2626

2727
useEffect(() => {
2828
if (!permissionsLoading && !isAdmin) {
@@ -71,7 +71,13 @@ export function ActivityPage() {
7171
</div>
7272
}
7373
>
74-
{entries.length === 0 ? (
74+
{error ? (
75+
<EmptyState
76+
icon={AlertTriangle}
77+
title="Failed to load activity"
78+
subtitle={error}
79+
/>
80+
) : entries.length === 0 ? (
7581
<EmptyState
7682
icon={Clock}
7783
title={entityTypeFilter ? 'No matching activity' : 'No activity yet'}

src/features/activity/useActivity.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ export function usePaginatedActivityLog(entityTypeFilter?: EntityTypeFilter, pag
1414
}
1515
}
1616

17-
const { items, totalCount, isLoading, isLoadingMore, hasMore, loadMore } = usePaginatedList<ActivityLogEntry>(
17+
const { items, totalCount, isLoading, isLoadingMore, hasMore, error, loadMore } = usePaginatedList<ActivityLogEntry>(
1818
basePath,
1919
[],
2020
pageSize,
2121
);
2222

23-
return { entries: items, totalCount, isLoading, isLoadingMore, hasMore, loadMore };
23+
return { entries: items, totalCount, isLoading, isLoadingMore, hasMore, error, loadMore };
2424
}

src/features/bins/BinListPage.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
AlertTriangle,
23
MapPin,
34
PackageOpen,
45
Plus,
@@ -78,7 +79,7 @@ export function BinListPage() {
7879
const { isAdmin, canWrite, canCreateBin } = usePermissions();
7980
const resetPage = useCallback(() => setPage(1), [setPage]);
8081
const { pageSize, setPageSize, pageSizeOptions } = usePageSize(resetPage);
81-
const { bins, totalCount, totalPages, isLoading } = usePaginatedBinList(debouncedSearch, sort, sortDir, filters, page, pageSize, setPage);
82+
const { bins, totalCount, totalPages, isLoading, error } = usePaginatedBinList(debouncedSearch, sort, sortDir, filters, page, pageSize, setPage);
8283
const allTags = useAllTags();
8384
const activeCount = countActiveFilters(filters);
8485
const { areas } = useAreaList(activeLocationId);
@@ -228,7 +229,13 @@ export function BinListPage() {
228229
isLoading={isLoading && bins.length === 0}
229230
skeleton={<BinListSkeleton viewMode={viewMode} />}
230231
>
231-
{bins.length === 0 ? (
232+
{error ? (
233+
<EmptyState
234+
icon={AlertTriangle}
235+
title={`Failed to load ${t.bins}`}
236+
subtitle={error}
237+
/>
238+
) : bins.length === 0 ? (
232239
search || activeCount > 0 ? (
233240
<EmptyState
234241
icon={PackageOpen}

src/features/locations/useLocations.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ function notifyLocationsChanged() {
1212
interface LocationsContextValue {
1313
locations: Location[];
1414
isLoading: boolean;
15+
error: string | null;
1516
}
1617

1718
export const LocationsContext = createContext<LocationsContextValue | null>(null);
1819

1920
export function LocationProvider({ children }: { children: React.ReactNode }) {
2021
const { token } = useAuth();
21-
const { data: locations, isLoading } = useListData<Location>(
22+
const { data: locations, isLoading, error } = useListData<Location>(
2223
token ? '/api/locations' : null,
2324
[Events.LOCATIONS],
2425
);
25-
const value = useMemo(() => ({ locations, isLoading }), [locations, isLoading]);
26+
const value = useMemo(() => ({ locations, isLoading, error }), [locations, isLoading, error]);
2627
return <LocationsContext.Provider value={value}>{children}</LocationsContext.Provider>;
2728
}
2829

src/lib/__tests__/usePermissions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('usePermissions', () => {
113113

114114
it('isLoading passes through from useLocationList', () => {
115115
setup('admin');
116-
mockUseLocationList.mockReturnValue({ locations: [], isLoading: true, refresh: vi.fn() } as ReturnType<typeof useLocationList>);
116+
mockUseLocationList.mockReturnValue({ locations: [], isLoading: true, error: null, refresh: vi.fn() } as ReturnType<typeof useLocationList>);
117117
const { result } = renderHook(() => usePermissions());
118118
expect(result.current.isLoading).toBe(true);
119119
});

0 commit comments

Comments
 (0)