218 lines
6.0 KiB
JavaScript
218 lines
6.0 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import { createElement } from 'react'
|
|
import { Flex, Alert, Button, Dropdown, Popover } from 'antd'
|
|
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
|
import { CaretDownOutlined } from '@ant-design/icons'
|
|
|
|
import { useMediaQuery } from 'react-responsive'
|
|
import { getModelByName } from '../../../database/ObjectModels'
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
const AlertsDisplay = ({
|
|
alerts = [],
|
|
printerId,
|
|
showDismiss = true,
|
|
showActions = true
|
|
}) => {
|
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
const getAlertType = (type, priority) => {
|
|
if (type === 'error' || priority === '9') return 'error'
|
|
if (type === 'warning' || priority === '8') return 'warning'
|
|
return 'info'
|
|
}
|
|
|
|
const printerModel = getModelByName('printer')
|
|
const navigate = useNavigate()
|
|
const getAlertIcon = (type, priority) => {
|
|
if (type === 'error' || priority === '9') return <ExclamationOctagonIcon />
|
|
if (type === 'warning' || priority === '8')
|
|
return <ExclamationOctagonIcon />
|
|
return <InfoCircleIcon />
|
|
}
|
|
|
|
// Recursively filter the printer model actions by a set of allowed action keys
|
|
const filterActionsByKeys = (actions, allowedKeys) => {
|
|
if (!Array.isArray(actions)) return []
|
|
|
|
const filtered = actions
|
|
.map((action) => {
|
|
if (action.type === 'divider') {
|
|
return { type: 'divider' }
|
|
}
|
|
|
|
const actionKey = action.key || action.name
|
|
let children = []
|
|
|
|
if (Array.isArray(action.children)) {
|
|
children = filterActionsByKeys(action.children, allowedKeys)
|
|
}
|
|
|
|
const isAllowed = actionKey && allowedKeys.has(actionKey)
|
|
|
|
if (!isAllowed && children.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
...action,
|
|
children
|
|
}
|
|
})
|
|
.filter((action) => action !== null)
|
|
|
|
// Clean up dividers: remove leading/trailing and consecutive dividers
|
|
const cleaned = []
|
|
for (const action of filtered) {
|
|
if (action.type === 'divider') {
|
|
if (cleaned.length === 0) continue
|
|
if (cleaned[cleaned.length - 1].type === 'divider') continue
|
|
}
|
|
cleaned.push(action)
|
|
}
|
|
if (cleaned[cleaned.length - 1]?.type === 'divider') {
|
|
cleaned.pop()
|
|
}
|
|
|
|
return cleaned
|
|
}
|
|
|
|
// Map filtered printer actions to AntD Dropdown menu items (including children)
|
|
const mapActionsToMenuItems = (actions) => {
|
|
if (!Array.isArray(actions)) return []
|
|
|
|
return actions.map((action) => {
|
|
if (action.type === 'divider') {
|
|
return { type: 'divider' }
|
|
}
|
|
|
|
const item = {
|
|
key: action.key || action.name,
|
|
label: action.label,
|
|
icon: action.icon ? createElement(action.icon) : undefined
|
|
}
|
|
|
|
if (Array.isArray(action.children) && action.children.length > 0) {
|
|
item.children = mapActionsToMenuItems(action.children)
|
|
}
|
|
|
|
return item
|
|
})
|
|
}
|
|
|
|
if (alerts.length == 0) {
|
|
return null
|
|
}
|
|
|
|
const alertElements = alerts.map((alert, index) => {
|
|
const printerActions = printerModel?.actions || []
|
|
|
|
const alertActionKeys = Array.isArray(alert?.actions)
|
|
? alert.actions
|
|
.map((action) =>
|
|
typeof action === 'string'
|
|
? action
|
|
: action?.key || action?.name || null
|
|
)
|
|
.filter((key) => key != null)
|
|
: []
|
|
|
|
const allowedKeys = new Set(alertActionKeys)
|
|
const filteredActions = filterActionsByKeys(printerActions, allowedKeys)
|
|
|
|
const findActionByKey = (actions, key) => {
|
|
if (!Array.isArray(actions)) return null
|
|
|
|
for (const action of actions) {
|
|
if (action.type === 'divider') continue
|
|
|
|
const actionKey = action.key || action.name
|
|
if (actionKey === key) {
|
|
return action
|
|
}
|
|
|
|
if (Array.isArray(action.children) && action.children.length > 0) {
|
|
const found = findActionByKey(action.children, key)
|
|
if (found) return found
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
const menu = {
|
|
items: mapActionsToMenuItems(filteredActions),
|
|
onClick: ({ key }) => {
|
|
const action = findActionByKey(filteredActions, key)
|
|
|
|
if (action?.url) {
|
|
navigate(action.url(printerId))
|
|
} else {
|
|
console.warn('No action found for key:', key)
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Alert
|
|
key={`${alert.createdAt}-${index}-${alert._id}`}
|
|
message={alert.message}
|
|
style={{ padding: '4px 10px 4px 8px' }}
|
|
type={getAlertType(alert.type, alert.priority)}
|
|
icon={getAlertIcon(alert.type, alert.priority)}
|
|
showIcon
|
|
closable={showDismiss && alert.canDismiss}
|
|
onClose={() => {
|
|
console.log('Closing alert:', alert._id)
|
|
}}
|
|
action={
|
|
showActions ? (
|
|
<Dropdown menu={menu} on>
|
|
<Button size='small' type='text' style={{ marginLeft: '5px' }}>
|
|
<CaretDownOutlined />
|
|
</Button>
|
|
</Dropdown>
|
|
) : null
|
|
}
|
|
/>
|
|
)
|
|
})
|
|
|
|
if (isMobile) {
|
|
return (
|
|
<Popover
|
|
content={alertElements}
|
|
trigger='hover'
|
|
arrow={false}
|
|
placement='bottom'
|
|
classNames={{
|
|
root: 'printer-alerts-display-popover'
|
|
}}
|
|
>
|
|
<Button>Alerts</Button>
|
|
</Popover>
|
|
)
|
|
}
|
|
|
|
return <Flex gap='small'>{alertElements}</Flex>
|
|
}
|
|
|
|
AlertsDisplay.propTypes = {
|
|
printerId: PropTypes.string.isRequired,
|
|
showActions: PropTypes.bool.isRequired,
|
|
showDismiss: PropTypes.bool.isRequired,
|
|
alerts: PropTypes.arrayOf(
|
|
PropTypes.shape({
|
|
canDismiss: PropTypes.bool.isRequired,
|
|
_id: PropTypes.string.isRequired,
|
|
type: PropTypes.string.isRequired,
|
|
createdAt: PropTypes.string.isRequired,
|
|
updatedAt: PropTypes.string.isRequired,
|
|
message: PropTypes.string,
|
|
actions: PropTypes.arrayOf(PropTypes.string)
|
|
})
|
|
).isRequired
|
|
}
|
|
|
|
export default AlertsDisplay
|