Refactor modal behavior across multiple components to use 'destroyOnHidden' instead of 'destroyOnClose' for improved resource management. This change applies to AppPasswordInfo, FilamentInfo, PartInfo, ProductInfo, UserInfo, MarketplaceInfo, and ExportListButton components.

This commit is contained in:
Tom Butcher 2026-07-23 21:41:46 +01:00
parent 899444e815
commit 634be89698
11 changed files with 140 additions and 49 deletions

View File

@ -191,7 +191,7 @@ const AppPasswordInfo = () => {
<Modal
open={regenerateSecretOpen}
destroyOnClose
destroyOnHidden={true}
width={650}
onCancel={() => {
actionHandlerRef.current?.clearAction?.()

View File

@ -229,7 +229,7 @@ const FilamentInfo = () => {
footer={null}
width={700}
onCancel={() => setNewFilamentSkuOpen(false)}
destroyOnClose
destroyOnHidden={true}
>
<NewFilamentSku
onOk={() => {

View File

@ -184,7 +184,7 @@ const PartInfo = () => {
footer={null}
width={700}
onCancel={() => setNewPartSkuOpen(false)}
destroyOnClose
destroyOnHidden={true}
>
<NewPartSku
onOk={() => {

View File

@ -188,7 +188,7 @@ const ProductInfo = () => {
footer={null}
width={700}
onCancel={() => setNewProductSkuOpen(false)}
destroyOnClose
destroyOnHidden={true}
>
<NewProductSku
onOk={() => {

View File

@ -239,7 +239,7 @@ const UserInfo = () => {
<Modal
open={newAppPasswordOpen}
destroyOnClose
destroyOnHidden={true}
width={700}
onCancel={() => {
actionHandlerRef.current?.clearAction?.()

View File

@ -1,6 +1,9 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import { Space, Flex, Card, Button, Modal } from 'antd'
import NewPrinterProfile, {
EditPrinterProfile
} from '../PrinterProfiles/NewPrinterProfile'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
@ -22,17 +25,20 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import PlusIcon from '../../../Icons/PlusIcon.jsx'
import PrinterProfileIcon from '../../../Icons/PrinterProfileIcon.jsx'
const log = loglevel.getLogger('PrinterInfo')
log.setLevel(config.logLevel)
const PrinterInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const profilesTableRef = useRef(null)
const actionHandlerRef = useRef(null)
const printerId = new URLSearchParams(location.search).get('printerId')
const [collapseState, updateCollapseState] = useCollapseState('PrinterInfo', {
info: true,
profiles: true,
stocks: true,
notes: true,
auditLogs: false
@ -46,6 +52,15 @@ const PrinterInfo = () => {
loading: false,
objectData: {}
})
const [profileModal, setProfileModal] = useState({
open: false,
profileId: null
})
const handleProfileSaved = () => {
setProfileModal({ open: false, profileId: null })
profilesTableRef.current?.reload()
}
const actions = {
edit: () => {
@ -85,6 +100,7 @@ const PrinterInfo = () => {
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Printer Information' },
{ key: 'profiles', label: 'Printer Profiles' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
@ -147,31 +163,73 @@ const PrinterInfo = () => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => {
return (
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printer'
objectData={objectData}
labelWidth='175px'
visibleProperties={{
currentFilamentStock: false,
'currentFilamentStock._id': false,
currentJob: false,
'currentJob._id': false,
currentSubJob: false,
'currentSubJob._id': false,
alerts: false
}}
/>
)
}}
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='printer'
objectData={objectData}
labelWidth='175px'
visibleProperties={{
currentFilamentStock: false,
'currentFilamentStock._id': false,
currentJob: false,
'currentJob._id': false,
currentSubJob: false,
'currentSubJob._id': false,
alerts: false
}}
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Printer Profiles'
icon={<PrinterProfileIcon />}
active={collapseState.profiles}
onToggle={(expanded) => updateCollapseState('profiles', expanded)}
collapseKey='profiles'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<Flex vertical gap='middle'>
<Flex>
<Button
icon={<PlusIcon />}
onClick={() =>
setProfileModal({ open: true, profileId: null })
}
>
New Printer Profile
</Button>
</Flex>
<ObjectTable
ref={profilesTableRef}
type='printerProfile'
masterFilter={{ printer: printerId }}
visibleColumns={{
printer: false,
'printer._id': false
}}
onRowAction={(action, profile) => {
if (action.name === 'edit') {
setProfileModal({
open: true,
profileId: profile._id
})
return true
}
return false
}}
/>
</Flex>
)}
</InfoCollapse>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
@ -206,6 +264,27 @@ const PrinterInfo = () => {
</Flex>
</ScrollBox>
</Flex>
<Modal
open={profileModal.open}
footer={null}
width={1000}
onCancel={() => setProfileModal({ open: false, profileId: null })}
destroyOnHidden
>
{profileModal.open &&
(profileModal.profileId ? (
<EditPrinterProfile
profileId={profileModal.profileId}
printerId={printerId}
onOk={handleProfileSaved}
/>
) : (
<NewPrinterProfile
printerId={printerId}
onOk={handleProfileSaved}
/>
))}
</Modal>
</>
)
}

View File

@ -285,7 +285,7 @@ const MarketplaceInfo = () => {
onCancel={() => setSyncListingsOpen(false)}
width={500}
footer={null}
destroyOnClose
destroyOnHidden={true}
centered
>
<SyncListings
@ -301,7 +301,7 @@ const MarketplaceInfo = () => {
onCancel={() => setSyncOrdersOpen(false)}
width={500}
footer={null}
destroyOnClose
destroyOnHidden={true}
centered
>
<SyncOrders
@ -317,7 +317,7 @@ const MarketplaceInfo = () => {
onCancel={() => setConfigureModalOpen(false)}
width={650}
footer={null}
destroyOnClose
destroyOnHidden={true}
centered
>
<ConfigureMarketplace

View File

@ -105,7 +105,7 @@ const ExportListButton = ({
</Dropdown>
<Modal
open={odataModalOpen}
destroyOnClose
destroyOnHidden={true}
width={650}
onCancel={() => setOdataModalOpen(false)}
footer={null}
@ -114,7 +114,7 @@ const ExportListButton = ({
</Modal>
<Modal
open={rssModalOpen}
destroyOnClose
destroyOnHidden={true}
width={750}
onCancel={() => setRssModalOpen(false)}
footer={null}

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Typography, Button, Tag, Badge, Flex, Card, Divider } from 'antd'
import {
@ -31,6 +31,10 @@ const Notification = ({
}) => {
const [deleting, setDeleting] = useState(false)
useEffect(() => {
console.log('notificationData', JSON.stringify(notification))
}, [notification])
const getNotificationIcon = (type) => {
switch (type) {
case 'info':
@ -220,7 +224,7 @@ const Notification = ({
{showExtraInfo && (
<>
<Divider style={{ margin: largeSpacing ? '10px 0' : 0 }} />
<Flex justify='space-between' align='center'>
<Flex ju stify='space-between' align='center'>
{getNotificationTag(notification.type)}
<TimeDisplay
dateTime={notification.createdAt}

View File

@ -47,17 +47,20 @@ const ObjectDisplay = ({
}, [])
// Detect minimal objects that only contain an _id (must be string, not populated object)
const isMinimalObject = useCallback((obj) => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
const keys = Object.keys(obj)
const id = obj._id
return (
keys.length === 1 &&
keys[0] === '_id' &&
typeof id === 'string' &&
isValidId(id)
)
}, [isValidId])
const isMinimalObject = useCallback(
(obj) => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
const keys = Object.keys(obj)
const id = obj._id
return (
keys.length === 1 &&
keys[0] === '_id' &&
typeof id === 'string' &&
isValidId(id)
)
},
[isValidId]
)
// If only an _id is provided, fetch the full object via spotlight
const fetchFullObjectIfNeeded = useCallback(
@ -131,7 +134,14 @@ const ObjectDisplay = ({
cancelled = true
setIsHydrating(false)
}
}, [object, fetchFullObjectIfNeeded, isMinimalObject, isValidId, getStringId, token])
}, [
object,
fetchFullObjectIfNeeded,
isMinimalObject,
isValidId,
getStringId,
token
])
if (!objectData) {
return <Text type='secondary'>n/a</Text>
}

View File

@ -43,8 +43,6 @@ const PropertyChanges = ({ type, value }) => {
...flatNew
}
console.log(combinedChanges)
return (
<Descriptions size='small' column={1}>
{Object.keys(combinedChanges).map((key) => {