Skip to content

Commit 8452c4f

Browse files
committed
Make sure users know about pending operations
1 parent 2e2e10c commit 8452c4f

File tree

6 files changed

+82
-27
lines changed

6 files changed

+82
-27
lines changed

examples/simple/src/Layout.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
InspectorButton,
77
TitlePortal,
88
useNotify,
9+
useIsOffine,
910
} from 'react-admin';
10-
import { onlineManager, useQueryClient } from '@tanstack/react-query';
11+
import { useQueryClient } from '@tanstack/react-query';
1112
import { Stack, Tooltip } from '@mui/material';
1213
import OfflineIcon from '@mui/icons-material/SignalWifiConnectedNoInternet4';
1314
import '../assets/app.css';
@@ -48,19 +49,6 @@ export const MyLayout = ({ children }) => (
4849
</>
4950
);
5051

51-
const useIsOffine = () => {
52-
const [isOnline, setIsOnline] = React.useState(onlineManager.isOnline());
53-
54-
React.useEffect(() => {
55-
const handleChange = () => {
56-
setIsOnline(onlineManager.isOnline());
57-
};
58-
return onlineManager.subscribe(handleChange);
59-
}, []);
60-
61-
return !isOnline;
62-
};
63-
6452
/**
6553
* When react-query resumes persisted mutations through their default functions (provided in the getOfflineFirstQueryClient file) after the browser tab
6654
* has been closed, it cannot handle their side effects unless we set up some defaults. In order to leverage the react-admin notification system

packages/ra-core/src/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export * from './SourceContext';
1515
export * from './useFirstResourceWithListAccess';
1616
export * from './useGetResourceLabel';
1717
export * from './useGetRecordRepresentation';
18+
export * from './useIsOffine';
1819
export * from './useResourceDefinitionContext';
1920
export * from './useResourceContext';
2021
export * from './useResourceDefinition';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react';
2+
import { onlineManager } from '@tanstack/react-query';
3+
4+
/**
5+
* Hook to determine if the application is offline.
6+
* It uses the onlineManager from react-query to check the online status.
7+
* It returns true if the application is offline, false otherwise.
8+
* @returns {boolean} - True if offline, false if online.
9+
*/
10+
export const useIsOffine = () => {
11+
const [isOnline, setIsOnline] = React.useState(onlineManager.isOnline());
12+
13+
React.useEffect(() => {
14+
const handleChange = () => {
15+
setIsOnline(onlineManager.isOnline());
16+
};
17+
return onlineManager.subscribe(handleChange);
18+
}, []);
19+
20+
return !isOnline;
21+
};

packages/ra-language-english/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ const englishMessages: TranslationMessages = {
174174
not_authorized: "You're not authorized to access this resource.",
175175
application_update_available: 'A new version is available.',
176176
offline: 'No connectivity. Could not fetch data.',
177+
pending_operations:
178+
'There is a pending operation due to network not being available |||| There are %{smart_count} pending operations due to network not being available',
177179
},
178180
validation: {
179181
required: 'Required',

packages/ra-language-french/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ const frenchMessages: TranslationMessages = {
182182
"Vous n'êtes pas autorisé(e) à accéder à cette ressource.",
183183
application_update_available: 'Une mise à jour est disponible.',
184184
offline: 'Pas de connexion. Impossible de charger les données.',
185+
pending_operations:
186+
'Il y a une opération en attente due à un problème de réseau |||| Il y a %{smart_count} opérations en attente dues à un problème de réseau',
185187
},
186188
validation: {
187189
required: 'Ce champ est requis',

packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import {
99
} from '@mui/material/styles';
1010
import clsx from 'clsx';
1111
import CircularProgress from '@mui/material/CircularProgress';
12-
import { useLoading } from 'ra-core';
12+
import { Translate, useIsOffine, useLoading } from 'ra-core';
1313

1414
import { RefreshIconButton, type RefreshIconButtonProps } from '../button';
15+
import { Badge, Tooltip } from '@mui/material';
16+
import { useMutationState } from '@tanstack/react-query';
1517

1618
export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
1719
const props = useThemeProps({
@@ -20,6 +22,12 @@ export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
2022
});
2123
const { className, onClick, sx, ...rest } = props;
2224
const loading = useLoading();
25+
const isOffline = useIsOffine();
26+
const pendingMutations = useMutationState({
27+
filters: {
28+
status: 'pending',
29+
},
30+
});
2331

2432
const theme = useTheme();
2533
return (
@@ -30,18 +38,51 @@ export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
3038
}`}
3139
onClick={onClick}
3240
/>
33-
{loading && (
34-
<CircularProgress
35-
className={clsx(
36-
'app-loader',
37-
LoadingIndicatorClasses.loader
38-
)}
39-
color="inherit"
40-
size={theme.spacing(2)}
41-
thickness={6}
42-
{...rest}
43-
/>
44-
)}
41+
{loading ? (
42+
isOffline ? (
43+
<Tooltip
44+
title={
45+
<Translate
46+
i18nKey="ra.notifications.pending_operations"
47+
options={{ smart_count: 1 }}
48+
>
49+
{pendingMutations.length > 1
50+
? `There are ${pendingMutations.length} pending
51+
operations due to network not being available`
52+
: `There is a pending operation due to network not being available`}
53+
</Translate>
54+
}
55+
>
56+
<Badge
57+
className={LoadingIndicatorClasses.loader}
58+
badgeContent={pendingMutations.length}
59+
color="primary"
60+
>
61+
<CircularProgress
62+
className={clsx(
63+
'app-loader',
64+
LoadingIndicatorClasses.loadedLoading
65+
)}
66+
color="inherit"
67+
size={theme.spacing(2)}
68+
thickness={6}
69+
{...rest}
70+
/>
71+
</Badge>
72+
</Tooltip>
73+
) : (
74+
<CircularProgress
75+
className={clsx(
76+
'app-loader',
77+
LoadingIndicatorClasses.loader
78+
)}
79+
color="inherit"
80+
size={theme.spacing(2)}
81+
thickness={6}
82+
{...rest}
83+
/>
84+
)
85+
) : null}
4586
</Root>
4687
);
4788
};

0 commit comments

Comments
 (0)