All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- 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.
320 lines
8.6 KiB
JavaScript
320 lines
8.6 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import {
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState
|
|
} from 'react'
|
|
import { Modal } from 'antd'
|
|
import ObjectInfo from '../../common/ObjectInfo'
|
|
import NewObjectForm from '../../common/NewObjectForm'
|
|
import WizardView from '../../common/WizardView'
|
|
import TemplatePreview from '../../common/TemplatePreview'
|
|
import ProgressDisplay from '../../common/ProgressDisplay'
|
|
import { ApiServerContext } from '../../context/ApiServerContext'
|
|
import { resolveEmailRecipients } from '../../utils/emailRecipients'
|
|
|
|
const progressByState = {
|
|
queued: 10,
|
|
rendering: 35,
|
|
sending: 70,
|
|
sent: 100,
|
|
error: 100
|
|
}
|
|
const EMPTY_RECIPIENT_CANDIDATES = []
|
|
const EMPTY_DEFAULT_VALUES = {}
|
|
|
|
const EmailWizardContent = ({
|
|
accounts,
|
|
defaultCandidates,
|
|
handleSubmit,
|
|
submitLoading,
|
|
objectData,
|
|
setObjectData,
|
|
form,
|
|
formValid,
|
|
onCreated
|
|
}) => {
|
|
const { fetchObject } = useContext(ApiServerContext)
|
|
const [candidates, setCandidates] = useState(defaultCandidates)
|
|
|
|
useEffect(() => {
|
|
if (defaultCandidates.length) {
|
|
setCandidates(defaultCandidates)
|
|
return
|
|
}
|
|
let cancelled = false
|
|
resolveEmailRecipients({
|
|
type: objectData?.objectType,
|
|
objectData: objectData?.object,
|
|
fetchObject
|
|
}).then((result) => {
|
|
if (!cancelled) setCandidates(result)
|
|
})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [
|
|
defaultCandidates,
|
|
fetchObject,
|
|
objectData?.object,
|
|
objectData?.objectType
|
|
])
|
|
|
|
useEffect(() => {
|
|
setObjectData((previous) => ({
|
|
...previous,
|
|
_recipientCandidates: candidates
|
|
}))
|
|
}, [candidates, setObjectData])
|
|
|
|
useEffect(() => {
|
|
const currentAccount = objectData?.emailAccount
|
|
const account =
|
|
accounts.find(
|
|
(entry) => entry._id === (currentAccount?._id || currentAccount)
|
|
) || accounts[0]
|
|
if (!account || objectData?.emailAccount) return
|
|
form.setFieldsValue({ emailAccount: account, fromEmail: account.fromEmail })
|
|
setObjectData((previous) => ({
|
|
...previous,
|
|
emailAccount: account,
|
|
fromEmail: account.fromEmail
|
|
}))
|
|
}, [accounts, form, objectData?.emailAccount, setObjectData])
|
|
|
|
useEffect(() => {
|
|
if (!candidates.length || objectData?.recipientEmail) return
|
|
const candidate = candidates[0]
|
|
const values = {
|
|
recipientEmail: candidate.email,
|
|
recipientType: candidate.recipientType,
|
|
recipient: candidate.recipient
|
|
}
|
|
form.setFieldsValue(values)
|
|
setObjectData((previous) => ({ ...previous, ...values }))
|
|
}, [candidates, form, objectData?.recipientEmail, setObjectData])
|
|
|
|
return (
|
|
<WizardView
|
|
steps={[
|
|
{
|
|
title: 'Required',
|
|
key: 'required',
|
|
content: (
|
|
<ObjectInfo
|
|
type='emailMessage'
|
|
column={1}
|
|
visibleProperties={{
|
|
name: true,
|
|
objectType: true,
|
|
object: true,
|
|
emailTemplate: true,
|
|
emailAccount: true,
|
|
recipientEmail: true,
|
|
recipientType: true,
|
|
recipient: true,
|
|
fromEmail: true
|
|
}}
|
|
bordered={false}
|
|
labelWidth={115}
|
|
isEditing
|
|
objectData={objectData}
|
|
/>
|
|
)
|
|
}
|
|
]}
|
|
submitText='Send'
|
|
title='Send Email'
|
|
formValid={
|
|
formValid && Boolean(objectData?.recipientEmail) && accounts.length > 0
|
|
}
|
|
loading={submitLoading}
|
|
sideBarGrow
|
|
sideBar={
|
|
<div
|
|
style={{
|
|
height: 'calc(100vh - 240px)',
|
|
flexGrow: 1,
|
|
minWidth: 0
|
|
}}
|
|
>
|
|
<TemplatePreview
|
|
objectData={objectData?.object}
|
|
template={objectData?.emailTemplate}
|
|
templateType='emailTemplate'
|
|
capabilities={{
|
|
htmlOnly: true,
|
|
widthControl: true,
|
|
defaultWidth: 600
|
|
}}
|
|
/>
|
|
</div>
|
|
}
|
|
onSubmit={async () => {
|
|
const result = await handleSubmit()
|
|
if (result?._id) onCreated(result)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
EmailWizardContent.propTypes = {
|
|
accounts: PropTypes.array.isRequired,
|
|
defaultCandidates: PropTypes.array.isRequired,
|
|
handleSubmit: PropTypes.func.isRequired,
|
|
submitLoading: PropTypes.bool,
|
|
objectData: PropTypes.object,
|
|
setObjectData: PropTypes.func.isRequired,
|
|
form: PropTypes.object.isRequired,
|
|
formValid: PropTypes.bool,
|
|
onCreated: PropTypes.func.isRequired
|
|
}
|
|
|
|
const NewEmailMessage = ({
|
|
onOk,
|
|
defaultValues = EMPTY_DEFAULT_VALUES,
|
|
recipientCandidates = EMPTY_RECIPIENT_CANDIDATES
|
|
}) => {
|
|
const {
|
|
fetchObject,
|
|
fetchObjects,
|
|
connected,
|
|
subscribeToObjectUpdates,
|
|
subscribeToObjectTypeUpdates
|
|
} = useContext(ApiServerContext)
|
|
const [accounts, setAccounts] = useState([])
|
|
const [sendingMessage, setSendingMessage] = useState(null)
|
|
const fetchObjectRef = useRef(fetchObject)
|
|
const fetchObjectsRef = useRef(fetchObjects)
|
|
const fetchedMessageIdRef = useRef(null)
|
|
fetchObjectRef.current = fetchObject
|
|
fetchObjectsRef.current = fetchObjects
|
|
const initialValues = useMemo(
|
|
() => ({ read: false, ...defaultValues }),
|
|
[defaultValues]
|
|
)
|
|
|
|
const loadAccounts = useCallback(async () => {
|
|
const result = await fetchObjectsRef.current('emailAccount', {
|
|
filter: { active: true },
|
|
limit: 100
|
|
})
|
|
setAccounts(Array.isArray(result?.data) ? result.data : [])
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
loadAccounts()
|
|
}, [loadAccounts])
|
|
|
|
useEffect(() => {
|
|
if (!connected) return undefined
|
|
return subscribeToObjectTypeUpdates(
|
|
'emailAccount',
|
|
{ active: true },
|
|
loadAccounts
|
|
)
|
|
}, [connected, loadAccounts, subscribeToObjectTypeUpdates])
|
|
|
|
useEffect(() => {
|
|
if (!connected) return undefined
|
|
const unsubscribes = accounts
|
|
.filter((account) => account?._id)
|
|
.map((account) =>
|
|
subscribeToObjectUpdates(
|
|
account._id.toLowerCase(),
|
|
'emailAccount',
|
|
(update) => {
|
|
const merged = { ...account, ...update }
|
|
setAccounts((previous) =>
|
|
merged.active === true
|
|
? previous.map((entry) =>
|
|
entry._id === account._id ? merged : entry
|
|
)
|
|
: previous.filter((entry) => entry._id !== account._id)
|
|
)
|
|
}
|
|
)
|
|
)
|
|
return () => unsubscribes.forEach((unsubscribe) => unsubscribe?.())
|
|
}, [accounts, connected, subscribeToObjectUpdates])
|
|
|
|
useEffect(() => {
|
|
const id = sendingMessage?._id
|
|
if (!id) return undefined
|
|
|
|
let cancelled = false
|
|
if (fetchedMessageIdRef.current !== id) {
|
|
fetchedMessageIdRef.current = id
|
|
fetchObjectRef.current(id, 'emailMessage').then((current) => {
|
|
if (!cancelled && current?._id) {
|
|
setSendingMessage((previous) => ({ ...previous, ...current }))
|
|
}
|
|
})
|
|
}
|
|
|
|
const unsubscribe =
|
|
connected === true
|
|
? subscribeToObjectUpdates(id.toLowerCase(), 'emailMessage', (update) =>
|
|
setSendingMessage((previous) => ({ ...previous, ...update }))
|
|
)
|
|
: undefined
|
|
|
|
return () => {
|
|
cancelled = true
|
|
unsubscribe?.()
|
|
}
|
|
}, [connected, sendingMessage?._id, subscribeToObjectUpdates])
|
|
|
|
useEffect(() => {
|
|
if (sendingMessage?.state?.type === 'sent') onOk?.(sendingMessage)
|
|
}, [onOk, sendingMessage])
|
|
|
|
return (
|
|
<>
|
|
<NewObjectForm type='emailMessage' defaultValues={initialValues}>
|
|
{(formProps) => (
|
|
<EmailWizardContent
|
|
{...formProps}
|
|
accounts={accounts}
|
|
defaultCandidates={recipientCandidates}
|
|
onCreated={setSendingMessage}
|
|
/>
|
|
)}
|
|
</NewObjectForm>
|
|
<Modal
|
|
open={Boolean(sendingMessage)}
|
|
footer={null}
|
|
closable={sendingMessage?.state?.type === 'error'}
|
|
onCancel={() => setSendingMessage(null)}
|
|
maskClosable={false}
|
|
centered
|
|
destroyOnHidden
|
|
zIndex={2000}
|
|
width={360}
|
|
getContainer={() => document.body}
|
|
>
|
|
<ProgressDisplay
|
|
percent={progressByState[sendingMessage?.state?.type] || 0}
|
|
status={
|
|
sendingMessage?.state?.type === 'error' ? 'exception' : 'active'
|
|
}
|
|
>
|
|
{sendingMessage?.state?.message ||
|
|
`Email ${sendingMessage?.state?.type || 'queued'}...`}
|
|
</ProgressDisplay>
|
|
</Modal>
|
|
</>
|
|
)
|
|
}
|
|
|
|
NewEmailMessage.propTypes = {
|
|
onOk: PropTypes.func,
|
|
defaultValues: PropTypes.object,
|
|
recipientCandidates: PropTypes.array
|
|
}
|
|
|
|
export default NewEmailMessage
|