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,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} />
</> </>
); );

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 (
<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>
); );
} }

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,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}