Tom Butcher 483682ce44
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Add Email Account and Email Message Management Components
- Introduced new EmailAccounts and EmailMessages components for managing email accounts and messages.
- Implemented ObjectTable for displaying data with filtering and sorting capabilities.
- Integrated InfoActionButtons for enhanced user interactions across various management functionalities.
- Refactored existing components to replace DocumentPrintButton with InfoActionButtons for consistency in action handling.
- Added EmailAccountInfo component for detailed view and management of individual email accounts.
2026-09-12 21:40:18 +01:00

216 lines
5.8 KiB
JavaScript

import PropTypes from 'prop-types'
import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState
} from 'react'
import { Button, Dropdown, Modal } from 'antd'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import MailIcon from '../../Icons/MailIcon'
import EmailTemplateIcon from '../../Icons/EmailTemplateIcon'
import NewEmailMessage from '../Management/EmailMessages/NewEmailMessage'
import {
getDefaultEmailRecipient,
resolveEmailRecipients
} from '../utils/emailRecipients'
const EmailSendButton = ({ type, objectData, disabled = false, ...buttonProps }) => {
const {
fetchObject,
fetchObjects,
connected,
subscribeToObjectUpdates,
subscribeToObjectTypeUpdates
} = useContext(ApiServerContext)
const { token } = useContext(AuthContext)
const [templates, setTemplates] = useState([])
const [recipients, setRecipients] = useState([])
const [selectedTemplate, setSelectedTemplate] = useState(null)
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const fetchObjectsRef = useRef(fetchObjects)
const fetchObjectRef = useRef(fetchObject)
fetchObjectsRef.current = fetchObjects
fetchObjectRef.current = fetchObject
const filter = useMemo(
() => ({ objectType: type, global: false, active: true }),
[type]
)
const matches = useCallback(
(template) =>
template?.objectType === type &&
template?.global === false &&
template?.active === true,
[type]
)
const loadTemplates = useCallback(
async (silent = false) => {
if (!type || token == null) return
if (!silent) setLoading(true)
try {
const result = await fetchObjectsRef.current('emailTemplate', {
filter,
limit: 100
})
setTemplates(Array.isArray(result?.data) ? result.data : [])
} finally {
if (!silent) setLoading(false)
}
},
[filter, token, type]
)
useEffect(() => {
loadTemplates()
}, [loadTemplates])
useEffect(() => {
let cancelled = false
setRecipients([])
resolveEmailRecipients({
type,
objectData,
fetchObject: (...args) => fetchObjectRef.current(...args)
}).then((result) => {
if (!cancelled) setRecipients(result)
})
return () => {
cancelled = true
}
}, [objectData, type])
useEffect(() => {
if (!connected) return undefined
return subscribeToObjectTypeUpdates('emailTemplate', filter, () =>
loadTemplates(true)
)
}, [
connected,
filter,
loadTemplates,
subscribeToObjectTypeUpdates
])
useEffect(() => {
if (!connected) return undefined
const unsubscribes = templates
.filter((template) => template?._id)
.map((template) =>
subscribeToObjectUpdates(
template._id.toLowerCase(),
'emailTemplate',
(update) => {
setTemplates((previous) => {
const merged = { ...template, ...update }
if (!matches(merged)) {
return previous.filter((entry) => entry._id !== template._id)
}
return previous.map((entry) =>
entry._id === template._id ? merged : entry
)
})
}
)
)
return () => unsubscribes.forEach((unsubscribe) => unsubscribe?.())
}, [connected, matches, subscribeToObjectUpdates, templates])
const menuItems = useMemo(() => {
const byTag = new Map()
const untagged = []
const makeItem = (template) => ({
key: template._id,
label: template.name,
icon: <EmailTemplateIcon />,
onClick: () => {
setSelectedTemplate(template)
setOpen(true)
}
})
templates.forEach((template) => {
const tags = Array.isArray(template.tags) ? template.tags.filter(Boolean) : []
if (!tags.length) {
untagged.push(template)
} else {
tags.forEach((tag) => byTag.set(tag, [...(byTag.get(tag) || []), template]))
}
})
return [
...untagged
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(makeItem),
...[...byTag.entries()]
.sort(([a], [b]) => a.localeCompare(b))
.map(([tag, taggedTemplates]) => ({
key: tag,
label: tag,
children: taggedTemplates
.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(makeItem)
}))
]
}, [templates])
const defaultRecipient = useMemo(
() => getDefaultEmailRecipient(recipients),
[recipients]
)
const unavailable = disabled || loading || !templates.length || !recipients.length
const title = !recipients.length
? 'No email recipients available for this object'
: !templates.length
? 'No email templates available for this object type'
: 'Send email'
return (
<>
<Dropdown menu={{ items: menuItems }} trigger={['hover']} disabled={unavailable}>
<Button
{...buttonProps}
icon={<MailIcon />}
disabled={unavailable}
loading={loading}
title={title}
/>
</Dropdown>
<Modal
open={open}
onCancel={() => setOpen(false)}
footer={null}
destroyOnHidden
width={{ xs: '100%', sm: '100%', md: '100%', lg: '90%', xl: '80%' }}
>
<NewEmailMessage
onOk={() => setOpen(false)}
defaultValues={{
objectType: type,
object: objectData,
emailTemplate: selectedTemplate,
...defaultRecipient
}}
recipientCandidates={recipients}
/>
</Modal>
</>
)
}
EmailSendButton.propTypes = {
type: PropTypes.string.isRequired,
objectData: PropTypes.object.isRequired,
disabled: PropTypes.bool
}
export default EmailSendButton