- Updated DocumentPrintButton to include subscription handling for document templates, improving real-time updates and user experience. - Refactored UserNotifierToggle to manage user notifier states with enhanced subscription logic, ensuring accurate updates and improved performance. - Introduced memoization and callback optimizations to streamline data fetching and state management in both components.
324 lines
8.8 KiB
JavaScript
324 lines
8.8 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import {
|
|
useState,
|
|
useEffect,
|
|
useContext,
|
|
useCallback,
|
|
useMemo,
|
|
useRef
|
|
} from 'react'
|
|
// import { getModelByName } from '../../../database/ObjectModels'
|
|
import DocumentPrinterIcon from '../../Icons/DocumentPrinterIcon'
|
|
import { Button, Dropdown, Modal } from 'antd'
|
|
import { ApiServerContext } from '../context/ApiServerContext'
|
|
import DocumentTemplateIcon from '../../Icons/DocumentTemplateIcon'
|
|
import NewDocumentJob from '../Management/DocumentJobs/NewDocumentJob'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
|
|
const DocumentPrintButton = ({
|
|
type,
|
|
objectData,
|
|
disabled = false,
|
|
...buttonProps
|
|
}) => {
|
|
const {
|
|
fetchObjects,
|
|
connected,
|
|
subscribeToObjectUpdates,
|
|
subscribeToObjectTypeUpdates
|
|
} = useContext(ApiServerContext)
|
|
const fetchObjectsRef = useRef(fetchObjects)
|
|
fetchObjectsRef.current = fetchObjects
|
|
|
|
const [documentTemplates, setDocumentTemplates] = useState([])
|
|
const [currentDocumentTemplate, setCurrentDocumentTemplate] = useState(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [newDocumentJobOpen, setNewDocumentJobOpen] = useState(false)
|
|
|
|
const subscribedIdsRef = useRef([])
|
|
const unsubscribesRef = useRef([])
|
|
const subscribeToObjectTypeUpdatesRef = useRef(null)
|
|
const updateEventHandlerRef = useRef()
|
|
|
|
const { token } = useContext(AuthContext)
|
|
|
|
const subscriptionFilter = useMemo(
|
|
() => ({
|
|
objectType: type,
|
|
global: false,
|
|
active: true
|
|
}),
|
|
[type]
|
|
)
|
|
|
|
const templateMatchesFilter = useCallback(
|
|
(template) =>
|
|
template?.objectType === type &&
|
|
template?.global === false &&
|
|
template?.active === true,
|
|
[type]
|
|
)
|
|
|
|
const loadDocumentTemplates = useCallback(
|
|
async (silent = false) => {
|
|
if (!type || token == null) return
|
|
|
|
if (!silent) {
|
|
setLoading(true)
|
|
}
|
|
try {
|
|
const result = await fetchObjectsRef.current('documentTemplate', {
|
|
filter: subscriptionFilter,
|
|
limit: 100 // Get more templates to show in dropdown
|
|
})
|
|
|
|
if (result && result.data) {
|
|
setDocumentTemplates(result.data)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching document templates:', error)
|
|
} finally {
|
|
if (!silent) {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
},
|
|
[type, token, subscriptionFilter]
|
|
)
|
|
|
|
const loadDocumentTemplatesRef = useRef(loadDocumentTemplates)
|
|
loadDocumentTemplatesRef.current = loadDocumentTemplates
|
|
|
|
// Stable key from objectData._id (excludes _isEditing so toggling edit mode doesn't trigger template reload)
|
|
const objectKey = useMemo(() => {
|
|
if (!objectData) return null
|
|
return objectData._id ?? objectData.id ?? null
|
|
}, [objectData])
|
|
|
|
// Fetch document templates when component mounts or objectData, type or token changes
|
|
useEffect(() => {
|
|
loadDocumentTemplates()
|
|
}, [objectKey, token, type, loadDocumentTemplates])
|
|
|
|
const updateEventHandler = useCallback(
|
|
(id, updatedData) => {
|
|
setDocumentTemplates((prev) => {
|
|
const existing = prev.find((template) => template._id === id)
|
|
const merged = existing
|
|
? { ...existing, ...updatedData }
|
|
: { _id: id, ...updatedData }
|
|
|
|
if (!templateMatchesFilter(merged)) {
|
|
return prev.filter((template) => template._id !== id)
|
|
}
|
|
|
|
if (existing) {
|
|
return prev.map((template) =>
|
|
template._id === id ? merged : template
|
|
)
|
|
}
|
|
|
|
return [...prev, merged]
|
|
})
|
|
},
|
|
[templateMatchesFilter]
|
|
)
|
|
|
|
updateEventHandlerRef.current = updateEventHandler
|
|
|
|
useEffect(() => {
|
|
if (connected !== true) return
|
|
|
|
const unsubscribe = subscribeToObjectTypeUpdates(
|
|
'documentTemplate',
|
|
subscriptionFilter,
|
|
() => loadDocumentTemplatesRef.current(true)
|
|
)
|
|
subscribeToObjectTypeUpdatesRef.current = unsubscribe
|
|
|
|
return () => {
|
|
if (unsubscribe) unsubscribe()
|
|
if (subscribeToObjectTypeUpdatesRef.current === unsubscribe) {
|
|
subscribeToObjectTypeUpdatesRef.current = null
|
|
}
|
|
}
|
|
}, [connected, subscriptionFilter, subscribeToObjectTypeUpdates])
|
|
|
|
useEffect(() => {
|
|
if (connected !== true) return
|
|
|
|
const templateIds = documentTemplates.map((template) => template._id).filter(Boolean)
|
|
|
|
const newTemplateIds = templateIds.filter(
|
|
(id) => !subscribedIdsRef.current.includes(id)
|
|
)
|
|
|
|
newTemplateIds.forEach((itemId) => {
|
|
const unsubscribe = subscribeToObjectUpdates(
|
|
itemId?.toLowerCase(),
|
|
'documentTemplate',
|
|
(updateData) => updateEventHandlerRef.current(itemId, updateData)
|
|
)
|
|
subscribedIdsRef.current.push(itemId)
|
|
if (unsubscribe) {
|
|
unsubscribesRef.current.push(unsubscribe)
|
|
}
|
|
})
|
|
|
|
const templateIdsToUnsubscribe = subscribedIdsRef.current.filter(
|
|
(id) => !templateIds.includes(id)
|
|
)
|
|
|
|
templateIdsToUnsubscribe.forEach((itemId) => {
|
|
const index = subscribedIdsRef.current.indexOf(itemId)
|
|
if (index > -1) {
|
|
subscribedIdsRef.current.splice(index, 1)
|
|
const unsubscribe = unsubscribesRef.current[index]
|
|
if (unsubscribe) {
|
|
unsubscribe()
|
|
}
|
|
unsubscribesRef.current.splice(index, 1)
|
|
}
|
|
})
|
|
}, [documentTemplates, connected, subscribeToObjectUpdates])
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
unsubscribesRef.current.forEach((unsubscribe) => {
|
|
if (unsubscribe) unsubscribe()
|
|
})
|
|
unsubscribesRef.current = []
|
|
subscribedIdsRef.current = []
|
|
|
|
if (subscribeToObjectTypeUpdatesRef.current) {
|
|
subscribeToObjectTypeUpdatesRef.current()
|
|
subscribeToObjectTypeUpdatesRef.current = null
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
// Handle template selection
|
|
const handleTemplateSelect = (template) => {
|
|
setCurrentDocumentTemplate(template)
|
|
setNewDocumentJobOpen(true)
|
|
// TODO: Implement the actual printing logic here
|
|
// This could open a print dialog, navigate to a print page, etc.
|
|
}
|
|
|
|
// Group templates by tag for nested dropdown menu; untagged templates at root
|
|
const menuItems = useMemo(() => {
|
|
const templatesByTag = new Map()
|
|
const untaggedTemplates = []
|
|
|
|
const toTemplateMenuItem = (template) => ({
|
|
key: template._id,
|
|
label: template.name,
|
|
icon: <DocumentTemplateIcon />,
|
|
onClick: () => handleTemplateSelect(template)
|
|
})
|
|
|
|
for (const template of documentTemplates) {
|
|
const templateTags = Array.isArray(template.tags)
|
|
? template.tags.filter(Boolean)
|
|
: []
|
|
|
|
if (templateTags.length === 0) {
|
|
untaggedTemplates.push(template)
|
|
continue
|
|
}
|
|
|
|
for (const tag of templateTags) {
|
|
const templates = templatesByTag.get(tag) ?? []
|
|
templates.push(template)
|
|
templatesByTag.set(tag, templates)
|
|
}
|
|
}
|
|
|
|
const taggedMenuItems = [...templatesByTag.keys()]
|
|
.sort((a, b) => a.localeCompare(b))
|
|
.map((tag) => ({
|
|
key: tag,
|
|
label: tag,
|
|
children: templatesByTag
|
|
.get(tag)
|
|
.slice()
|
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
.map(toTemplateMenuItem)
|
|
}))
|
|
|
|
const rootUntaggedItems = untaggedTemplates
|
|
.slice()
|
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
.map(toTemplateMenuItem)
|
|
|
|
return [...rootUntaggedItems, ...taggedMenuItems]
|
|
}, [documentTemplates])
|
|
|
|
// If no templates available, show disabled state
|
|
if (documentTemplates.length === 0 && !loading) {
|
|
return (
|
|
<Button
|
|
{...buttonProps}
|
|
icon={<DocumentPrinterIcon />}
|
|
disabled={true}
|
|
title='No document templates available for this object type'
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Dropdown
|
|
menu={{
|
|
items: menuItems,
|
|
loading: loading
|
|
}}
|
|
trigger={['hover']}
|
|
disabled={disabled || loading}
|
|
>
|
|
<Button
|
|
{...buttonProps}
|
|
icon={<DocumentPrinterIcon />}
|
|
disabled={disabled || loading}
|
|
loading={loading}
|
|
title={loading ? 'Loading templates...' : 'Print document'}
|
|
/>
|
|
</Dropdown>
|
|
<Modal
|
|
open={newDocumentJobOpen}
|
|
onCancel={() => setNewDocumentJobOpen(false)}
|
|
footer={null}
|
|
destroyOnHidden={true}
|
|
width={{
|
|
xs: '100%',
|
|
sm: '100%',
|
|
md: '100%',
|
|
lg: '90%',
|
|
xl: '80%',
|
|
xxl: '80%'
|
|
}}
|
|
>
|
|
<NewDocumentJob
|
|
onOk={() => {
|
|
setNewDocumentJobOpen(false)
|
|
}}
|
|
reset={!newDocumentJobOpen}
|
|
defaultValues={{
|
|
objectType: type,
|
|
object: objectData,
|
|
documentTemplate: currentDocumentTemplate
|
|
}}
|
|
/>
|
|
</Modal>
|
|
</>
|
|
)
|
|
}
|
|
|
|
DocumentPrintButton.propTypes = {
|
|
type: PropTypes.string.isRequired,
|
|
disabled: PropTypes.bool,
|
|
objectData: PropTypes.object.isRequired
|
|
}
|
|
|
|
export default DocumentPrintButton
|