2
0
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:
Oliver 2025-03-24 20:45:34 +11:00 committed by GitHub
parent 8997f193c9
commit 136e179cc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 128 additions and 35 deletions

View File

@ -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,6 +12,7 @@ export function ScanButton() {
return ( return (
<> <>
<Tooltip position='bottom-end' label={t`Scan Barcode`}>
<ActionIcon <ActionIcon
onClick={open} onClick={open}
variant='transparent' variant='transparent'
@ -19,6 +20,7 @@ export function ScanButton() {
> >
<IconQrcode /> <IconQrcode />
</ActionIcon> </ActionIcon>
</Tooltip>
<BarcodeScanDialog opened={opened} onClose={close} /> <BarcodeScanDialog opened={opened} onClose={close} />
</> </>
); );

View File

@ -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 (
<Tooltip position='bottom-end' label={t`Open spotlight`}>
<ActionIcon <ActionIcon
onClick={() => firstSpotlight.open()} onClick={() => firstSpotlight.open()}
title={t`Open spotlight`}
variant='transparent' variant='transparent'
aria-label='open-spotlight' aria-label='open-spotlight'
> >
<IconCommand /> <IconCommand />
</ActionIcon> </ActionIcon>
</Tooltip>
); );
} }

View File

@ -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,6 +184,7 @@ export function Header() {
</Text> </Text>
)} )}
<Group> <Group>
<Tooltip position='bottom-end' label={t`Search`}>
<ActionIcon <ActionIcon
onClick={openSearchDrawer} onClick={openSearchDrawer}
variant='transparent' variant='transparent'
@ -132,6 +192,7 @@ export function Header() {
> >
<IconSearch /> <IconSearch />
</ActionIcon> </ActionIcon>
</Tooltip>
<SpotlightButton /> <SpotlightButton />
{globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />} {globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />}
<Indicator <Indicator
@ -142,6 +203,7 @@ export function Header() {
disabled={notificationCount <= 0} disabled={notificationCount <= 0}
inline inline
> >
<Tooltip position='bottom-end' label={t`Notifications`}>
<ActionIcon <ActionIcon
onClick={openNotificationDrawer} onClick={openNotificationDrawer}
variant='transparent' variant='transparent'
@ -149,7 +211,37 @@ export function Header() {
> >
<IconBell /> <IconBell />
</ActionIcon> </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}