mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
[UI] Alerts (#9365)
* remove TODO (now implemented) * Add Mantine tooltips * Add "alerts" to header * Add more alert types
This commit is contained in:
parent
8997f193c9
commit
136e179cc4
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon } from '@mantine/core';
|
import { ActionIcon, Tooltip } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconQrcode } from '@tabler/icons-react';
|
import { IconQrcode } from '@tabler/icons-react';
|
||||||
import BarcodeScanDialog from '../barcodes/BarcodeScanDialog';
|
import BarcodeScanDialog from '../barcodes/BarcodeScanDialog';
|
||||||
@ -12,13 +12,15 @@ export function ScanButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ActionIcon
|
<Tooltip position='bottom-end' label={t`Scan Barcode`}>
|
||||||
onClick={open}
|
<ActionIcon
|
||||||
variant='transparent'
|
onClick={open}
|
||||||
title={t`Open Barcode Scanner`}
|
variant='transparent'
|
||||||
>
|
title={t`Open Barcode Scanner`}
|
||||||
<IconQrcode />
|
>
|
||||||
</ActionIcon>
|
<IconQrcode />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
<BarcodeScanDialog opened={opened} onClose={close} />
|
<BarcodeScanDialog opened={opened} onClose={close} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon } from '@mantine/core';
|
import { ActionIcon, Tooltip } from '@mantine/core';
|
||||||
import { IconCommand } from '@tabler/icons-react';
|
import { IconCommand } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { firstSpotlight } from '../nav/Layout';
|
import { firstSpotlight } from '../nav/Layout';
|
||||||
@ -9,13 +9,14 @@ import { firstSpotlight } from '../nav/Layout';
|
|||||||
*/
|
*/
|
||||||
export function SpotlightButton() {
|
export function SpotlightButton() {
|
||||||
return (
|
return (
|
||||||
<ActionIcon
|
<Tooltip position='bottom-end' label={t`Open spotlight`}>
|
||||||
onClick={() => firstSpotlight.open()}
|
<ActionIcon
|
||||||
title={t`Open spotlight`}
|
onClick={() => firstSpotlight.open()}
|
||||||
variant='transparent'
|
variant='transparent'
|
||||||
aria-label='open-spotlight'
|
aria-label='open-spotlight'
|
||||||
>
|
>
|
||||||
<IconCommand />
|
<IconCommand />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
Alert,
|
||||||
Container,
|
Container,
|
||||||
Group,
|
Group,
|
||||||
Indicator,
|
Indicator,
|
||||||
|
Menu,
|
||||||
Tabs,
|
Tabs,
|
||||||
Text
|
Text,
|
||||||
|
Tooltip
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconBell, IconSearch } from '@tabler/icons-react';
|
import {
|
||||||
|
IconBell,
|
||||||
|
IconExclamationCircle,
|
||||||
|
IconSearch
|
||||||
|
} from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
||||||
import { useMatch, useNavigate } from 'react-router-dom';
|
import { useMatch, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { getNavTabs } from '../../defaults/links';
|
import { getNavTabs } from '../../defaults/links';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
@ -32,7 +40,15 @@ import { NavigationDrawer } from './NavigationDrawer';
|
|||||||
import { NotificationDrawer } from './NotificationDrawer';
|
import { NotificationDrawer } from './NotificationDrawer';
|
||||||
import { SearchDrawer } from './SearchDrawer';
|
import { SearchDrawer } from './SearchDrawer';
|
||||||
|
|
||||||
|
interface AlertInfo {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
|
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
|
||||||
state.setNavigationOpen,
|
state.setNavigationOpen,
|
||||||
state.navigationOpen
|
state.navigationOpen
|
||||||
@ -58,6 +74,49 @@ export function Header() {
|
|||||||
return server.customize?.navbar_message;
|
return server.customize?.navbar_message;
|
||||||
}, [server.customize]);
|
}, [server.customize]);
|
||||||
|
|
||||||
|
const [dismissed, setDismissed] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const alerts: AlertInfo[] = useMemo(() => {
|
||||||
|
const _alerts: AlertInfo[] = [];
|
||||||
|
|
||||||
|
if (server?.debug_mode) {
|
||||||
|
_alerts.push({
|
||||||
|
key: 'debug',
|
||||||
|
title: t`Debug Mode`,
|
||||||
|
message: t`The server is running in debug mode.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server?.worker_running == false) {
|
||||||
|
_alerts.push({
|
||||||
|
key: 'worker',
|
||||||
|
title: t`Background Worker`,
|
||||||
|
message: t`The background worker process is not running.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalSettings.isSet('SERVER_RESTART_REQUIRED')) {
|
||||||
|
_alerts.push({
|
||||||
|
key: 'restart',
|
||||||
|
title: t`Server Restart`,
|
||||||
|
message: t`The server requires a restart to apply changes.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const n_migrations =
|
||||||
|
Number.parseInt(globalSettings.getSetting('_PENDING_MIGRATIONS')) ?? 0;
|
||||||
|
|
||||||
|
if (n_migrations > 0) {
|
||||||
|
_alerts.push({
|
||||||
|
key: 'migrations',
|
||||||
|
title: t`Database Migrations`,
|
||||||
|
message: t`There are pending database migrations.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return _alerts.filter((alert) => !dismissed.includes(alert.key));
|
||||||
|
}, [server, dismissed, globalSettings]);
|
||||||
|
|
||||||
// Fetch number of notifications for the current user
|
// Fetch number of notifications for the current user
|
||||||
const notifications = useQuery({
|
const notifications = useQuery({
|
||||||
queryKey: ['notification-count'],
|
queryKey: ['notification-count'],
|
||||||
@ -125,13 +184,15 @@ export function Header() {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon
|
<Tooltip position='bottom-end' label={t`Search`}>
|
||||||
onClick={openSearchDrawer}
|
<ActionIcon
|
||||||
variant='transparent'
|
onClick={openSearchDrawer}
|
||||||
aria-label='open-search'
|
variant='transparent'
|
||||||
>
|
aria-label='open-search'
|
||||||
<IconSearch />
|
>
|
||||||
</ActionIcon>
|
<IconSearch />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
<SpotlightButton />
|
<SpotlightButton />
|
||||||
{globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />}
|
{globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />}
|
||||||
<Indicator
|
<Indicator
|
||||||
@ -142,14 +203,45 @@ export function Header() {
|
|||||||
disabled={notificationCount <= 0}
|
disabled={notificationCount <= 0}
|
||||||
inline
|
inline
|
||||||
>
|
>
|
||||||
<ActionIcon
|
<Tooltip position='bottom-end' label={t`Notifications`}>
|
||||||
onClick={openNotificationDrawer}
|
<ActionIcon
|
||||||
variant='transparent'
|
onClick={openNotificationDrawer}
|
||||||
aria-label='open-notifications'
|
variant='transparent'
|
||||||
>
|
aria-label='open-notifications'
|
||||||
<IconBell />
|
>
|
||||||
</ActionIcon>
|
<IconBell />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
</Indicator>
|
</Indicator>
|
||||||
|
{user.isStaff() && alerts.length > 0 && (
|
||||||
|
<Menu withinPortal={true} position='bottom-end'>
|
||||||
|
<Menu.Target>
|
||||||
|
<Tooltip position='bottom-end' label={t`Alerts`}>
|
||||||
|
<ActionIcon
|
||||||
|
variant='transparent'
|
||||||
|
aria-label='open-alerts'
|
||||||
|
color='red'
|
||||||
|
>
|
||||||
|
<IconExclamationCircle />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
{alerts.map((alert) => (
|
||||||
|
<Menu.Item key={`alert-item-${alert.key}`}>
|
||||||
|
<Alert
|
||||||
|
withCloseButton
|
||||||
|
color='red'
|
||||||
|
title={alert.title}
|
||||||
|
onClose={() => setDismissed([...dismissed, alert.key])}
|
||||||
|
>
|
||||||
|
{alert.message}
|
||||||
|
</Alert>
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
<MainMenu />
|
<MainMenu />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
@ -179,8 +271,6 @@ function NavTabs() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Hide icons if user does not wish to display them!
|
|
||||||
|
|
||||||
_tabs.push(
|
_tabs.push(
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
value={tab.name}
|
value={tab.name}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user