2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 03:26:45 +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 { ActionIcon } from '@mantine/core';
import { ActionIcon, Tooltip } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconQrcode } from '@tabler/icons-react';
import BarcodeScanDialog from '../barcodes/BarcodeScanDialog';
@ -12,13 +12,15 @@ export function ScanButton() {
return (
<>
<ActionIcon
onClick={open}
variant='transparent'
title={t`Open Barcode Scanner`}
>
<IconQrcode />
</ActionIcon>
<Tooltip position='bottom-end' label={t`Scan Barcode`}>
<ActionIcon
onClick={open}
variant='transparent'
title={t`Open Barcode Scanner`}
>
<IconQrcode />
</ActionIcon>
</Tooltip>
<BarcodeScanDialog opened={opened} onClose={close} />
</>
);

View File

@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
import { ActionIcon } from '@mantine/core';
import { ActionIcon, Tooltip } from '@mantine/core';
import { IconCommand } from '@tabler/icons-react';
import { firstSpotlight } from '../nav/Layout';
@ -9,13 +9,14 @@ import { firstSpotlight } from '../nav/Layout';
*/
export function SpotlightButton() {
return (
<ActionIcon
onClick={() => firstSpotlight.open()}
title={t`Open spotlight`}
variant='transparent'
aria-label='open-spotlight'
>
<IconCommand />
</ActionIcon>
<Tooltip position='bottom-end' label={t`Open spotlight`}>
<ActionIcon
onClick={() => firstSpotlight.open()}
variant='transparent'
aria-label='open-spotlight'
>
<IconCommand />
</ActionIcon>
</Tooltip>
);
}

View File

@ -1,17 +1,25 @@
import {
ActionIcon,
Alert,
Container,
Group,
Indicator,
Menu,
Tabs,
Text
Text,
Tooltip
} from '@mantine/core';
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 { type ReactNode, useEffect, useMemo, useState } from 'react';
import { useMatch, useNavigate } from 'react-router-dom';
import { t } from '@lingui/macro';
import { api } from '../../App';
import { getNavTabs } from '../../defaults/links';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
@ -32,7 +40,15 @@ import { NavigationDrawer } from './NavigationDrawer';
import { NotificationDrawer } from './NotificationDrawer';
import { SearchDrawer } from './SearchDrawer';
interface AlertInfo {
key: string;
title: string;
message: string;
}
export function Header() {
const user = useUserState();
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
state.setNavigationOpen,
state.navigationOpen
@ -58,6 +74,49 @@ export function Header() {
return server.customize?.navbar_message;
}, [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
const notifications = useQuery({
queryKey: ['notification-count'],
@ -125,13 +184,15 @@ export function Header() {
</Text>
)}
<Group>
<ActionIcon
onClick={openSearchDrawer}
variant='transparent'
aria-label='open-search'
>
<IconSearch />
</ActionIcon>
<Tooltip position='bottom-end' label={t`Search`}>
<ActionIcon
onClick={openSearchDrawer}
variant='transparent'
aria-label='open-search'
>
<IconSearch />
</ActionIcon>
</Tooltip>
<SpotlightButton />
{globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />}
<Indicator
@ -142,14 +203,45 @@ export function Header() {
disabled={notificationCount <= 0}
inline
>
<ActionIcon
onClick={openNotificationDrawer}
variant='transparent'
aria-label='open-notifications'
>
<IconBell />
</ActionIcon>
<Tooltip position='bottom-end' label={t`Notifications`}>
<ActionIcon
onClick={openNotificationDrawer}
variant='transparent'
aria-label='open-notifications'
>
<IconBell />
</ActionIcon>
</Tooltip>
</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 />
</Group>
</Group>
@ -179,8 +271,6 @@ function NavTabs() {
return;
}
// TODO: Hide icons if user does not wish to display them!
_tabs.push(
<Tabs.Tab
value={tab.name}