Add Email Account and Email Message Management Components
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.
This commit is contained in:
Tom Butcher 2026-09-12 21:40:18 +01:00
parent 098fec3b2e
commit 483682ce44
83 changed files with 3266 additions and 153 deletions

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.881711,0,0,0.881711,3,-0.216913)">
<path d="M0,36.625C0,57 14.062,69.844 34.156,69.844C39.234,69.844 43.125,69.328 45.578,68.453C47.531,67.75 48.266,66.672 48.266,65.156C48.266,63.531 47.203,62.391 45.266,62.391C44.688,62.391 44.109,62.453 43.438,62.547C40.844,63.094 38.5,63.484 35.219,63.484C18.266,63.484 7.078,53.359 7.078,36.797C7.078,20.641 17.812,9.578 33.641,9.578C48.109,9.578 58.953,18.625 58.953,33.953C58.953,41.922 56.359,47.25 52.203,47.25C49.438,47.25 47.891,45.422 47.891,42.266L47.891,24.203C47.891,21.688 46.516,20.25 44.062,20.25C41.656,20.25 40.266,21.688 40.266,24.203L40.266,26.188L39.703,26.188C38.125,22.453 34.5,20.25 30,20.25C22.062,20.25 16.625,26.891 16.625,36.703C16.625,46.75 22.188,53.609 30.469,53.609C35.172,53.609 38.516,51.266 40.234,47.062L40.766,47.062C41.719,51.156 45.438,53.578 50.5,53.578C59.984,53.578 65.781,45.391 65.781,33.203C65.781,15.172 52.547,3.234 33.672,3.234C13.828,3.234 0,16.672 0,36.625ZM32.375,46.75C27.922,46.75 25.156,43 25.156,36.891C25.156,30.828 27.953,27.047 32.391,27.047C36.906,27.047 39.734,30.781 39.734,36.828C39.734,42.938 36.875,46.75 32.375,46.75Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.550669,0,0,0.550669,3.539221,19.893693)">
<path d="M12.25,59.219L69.375,59.219C77,59.219 81.531,54.688 81.531,46.969L81.531,12.281C81.531,4.562 77,0.031 69.25,0.031L12.156,0.031C4.531,0.031 0,4.562 0,12.281L0,46.969C0,54.688 4.531,59.219 12.25,59.219ZM13.031,49.094C11.156,49.094 10.125,48.062 10.125,46.156L10.125,13.094C10.125,11.188 11.156,10.156 13.031,10.156L68.469,10.156C70.344,10.156 71.406,11.188 71.406,13.094L71.406,46.156C71.406,48.062 70.344,49.094 68.469,49.094L13.031,49.094ZM40.75,39.594C43.531,39.594 45.562,38.719 48.031,36.5L76.344,11L70.406,5.031L42.75,30C42.094,30.562 41.469,30.906 40.75,30.906C40.062,30.906 39.438,30.562 38.781,30L11.125,5.031L5.156,11L33.5,36.5C35.938,38.719 38,39.594 40.75,39.594ZM11.875,52.531L33.656,30.75L27.531,24.906L5.906,46.531L11.875,52.531ZM69.656,52.531L75.625,46.531L54,24.906L47.875,30.75L69.656,52.531Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.712671,0,0,0.701139,14.732018,4.477477)">
<path d="M60.461,23.664L63.969,20.107L63.969,20.931C63.969,22.899 62.397,24.496 60.461,24.496C58.525,24.496 56.953,22.899 56.953,20.931L56.953,20.107L60.461,23.664ZM60.461,23.664L56.953,20.099C56.953,19.993 56.948,19.889 56.938,19.787C56.744,17.829 58.15,16.079 60.077,15.882C62.003,15.685 63.724,17.115 63.918,19.073C63.952,19.41 63.969,19.752 63.969,20.099L60.461,23.664ZM60.461,49.46L56.953,45.886L56.953,45.063C56.953,43.095 58.525,41.497 60.461,41.497C62.397,41.497 63.969,43.095 63.969,45.063L63.969,45.886L60.461,49.46ZM60.461,49.46L63.969,45.895C63.969,46.241 63.952,46.583 63.918,46.921C63.724,48.879 62.003,50.308 60.077,50.111C58.15,49.914 56.744,48.164 56.938,46.206C56.948,46.104 56.953,46 56.953,45.895L60.461,49.46ZM53.959,56.069L53.043,56.069C51.107,56.069 49.535,54.472 49.535,52.504C49.535,50.536 51.107,48.938 53.043,48.938L53.949,48.938L53.959,56.069ZM53.959,56.069L53.959,48.938C54.063,48.938 54.165,48.933 54.266,48.922C56.192,48.725 57.913,50.155 58.107,52.113C58.301,54.071 56.895,55.821 54.968,56.018C54.636,56.052 54.3,56.069 53.959,56.069ZM4.873,23.664L1.365,20.099C1.365,19.752 1.382,19.41 1.415,19.073C1.609,17.115 3.33,15.685 5.257,15.882C7.183,16.079 8.59,17.829 8.396,19.787C8.386,19.889 8.38,19.993 8.38,20.099L4.873,23.664ZM4.873,23.664L8.38,20.107L8.38,20.931C8.38,22.899 6.809,24.496 4.873,24.496C2.936,24.496 1.365,22.899 1.365,20.931L1.365,20.107L4.873,23.664ZM11.374,17.055C11.271,17.055 11.169,17.061 11.068,17.071C9.142,17.268 7.42,15.838 7.226,13.88C7.032,11.922 8.439,10.173 10.365,9.976C10.697,9.942 11.034,9.924 11.374,9.924L11.374,17.055ZM11.374,17.055L11.384,9.924L12.29,9.924C14.226,9.924 15.798,11.522 15.798,13.49C15.798,15.458 14.226,17.055 12.29,17.055L11.374,17.055ZM53.959,17.055L53.043,17.055C51.107,17.055 49.535,15.458 49.535,13.49C49.535,11.522 51.107,9.924 53.043,9.924L53.949,9.924L53.959,17.055ZM53.959,17.055L53.959,9.924C54.3,9.924 54.636,9.942 54.968,9.976C56.895,10.173 58.301,11.922 58.107,13.88C57.913,15.838 56.192,17.268 54.266,17.071C54.165,17.061 54.063,17.055 53.959,17.055ZM56.953,32.165C56.953,30.197 58.525,28.599 60.461,28.599C62.397,28.599 63.969,30.197 63.969,32.165L63.969,33.829C63.969,35.797 62.397,37.394 60.461,37.394C58.525,37.394 56.953,35.797 56.953,33.829L56.953,32.165ZM24.654,17.055C22.717,17.055 21.146,15.458 21.146,13.49C21.146,11.522 22.717,9.924 24.654,9.924L26.485,9.924C28.421,9.924 29.993,11.522 29.993,13.49C29.993,15.458 28.421,17.055 26.485,17.055L24.654,17.055ZM38.848,17.055C36.912,17.055 35.341,15.458 35.341,13.49C35.341,11.522 36.912,9.924 38.848,9.924L40.68,9.924C42.616,9.924 44.188,11.522 44.188,13.49C44.188,15.458 42.616,17.055 40.68,17.055L38.848,17.055Z"/>
</g>
<g transform="matrix(0.643125,0.667237,-0.65644,0.632719,30.88979,-37.900014)">
<path d="M57.763,28.143C57.763,26.629 58.972,25.401 60.461,25.401C61.95,25.401 63.159,26.629 63.159,28.143L63.159,29.252C63.159,30.765 61.95,31.994 60.461,31.994C58.972,31.994 57.763,30.765 57.763,29.252L57.763,28.143Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -24,7 +24,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
@ -131,7 +131,7 @@ const InvoiceInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='invoice' type='invoice'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { import {
getModelProperty, getModelProperty,
@ -121,7 +121,7 @@ const PaymentPolicyInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='paymentPolicy' type='paymentPolicy'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -121,7 +121,7 @@ const PaymentInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='payment' type='payment'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -112,7 +112,7 @@ const TaxRecordInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='taxRecord' type='taxRecord'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import NoteIcon from '../../../Icons/NoteIcon'
import AuditLogIcon from '../../../Icons/AuditLogIcon' import AuditLogIcon from '../../../Icons/AuditLogIcon'
import GraphIcon from '../../../Icons/GraphIcon' import GraphIcon from '../../../Icons/GraphIcon'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
import DocumentPrintButton from '../../common/DocumentPrintButton' import InfoActionButtons from '../../common/InfoActionButtons'
import UserNotifierToggle from '../../common/UserNotifierToggle' import UserNotifierToggle from '../../common/UserNotifierToggle'
import ScrollBox from '../../common/ScrollBox' import ScrollBox from '../../common/ScrollBox'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
@ -116,7 +116,7 @@ const FilamentStockInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='filamentStock' type='filamentStock'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,6 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -115,6 +116,11 @@ const OrderItemInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<InfoActionButtons
type='orderItem'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space> </Space>
<ActivityIndicator <ActivityIndicator
activities={objectFormState.activities} activities={objectFormState.activities}

View File

@ -22,7 +22,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import HistoryDisplay from '../../common/HistoryDisplay.jsx' import HistoryDisplay from '../../common/HistoryDisplay.jsx'
@ -123,7 +123,7 @@ const PartStockInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='partStock' type='partStock'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -30,7 +30,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import HistoryDisplay from '../../common/HistoryDisplay.jsx' import HistoryDisplay from '../../common/HistoryDisplay.jsx'
@ -140,7 +140,7 @@ const ProductStockInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='productStock' type='productStock'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx' import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
@ -143,7 +143,7 @@ const PurchaseOrderInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='purchaseOrder' type='purchaseOrder'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx' import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
import NewOrderItem from '../OrderItems/NewOrderItem.jsx' import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
@ -146,7 +146,7 @@ const PurchaseOrderInfo = () => {
visibleState={collapseState} visibleState={collapseState}
updateVisibleState={updateCollapseState} updateVisibleState={updateCollapseState}
/> />
<DocumentPrintButton <InfoActionButtons
type='purchaseOrder' type='purchaseOrder'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
@ -124,7 +124,7 @@ const ShipmentInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='shipment' type='shipment'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -24,7 +24,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -122,7 +122,7 @@ const StockAuditInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='stockAudit' type='stockAudit'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -25,7 +25,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -130,7 +130,7 @@ const StockLocationInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='stockLocation' type='stockLocation'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -24,7 +24,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -128,7 +128,7 @@ const StockTransferInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='stockTransfer' type='stockTransfer'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -18,7 +18,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -113,7 +113,7 @@ const AppPasswordInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='appPassword' type='appPassword'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -118,7 +118,7 @@ const CourierServiceInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='courierService' type='courierService'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
@ -110,7 +110,7 @@ const CourierInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='courier' type='courier'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -114,7 +114,7 @@ const DocumentJobInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='documentJob' type='documentJob'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -122,7 +122,7 @@ const DocumentPrinterInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='documentPrinter' type='documentPrinter'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -114,7 +114,7 @@ const DocumentSizeInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='documentSize' type='documentSize'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -119,7 +119,7 @@ const DocumentTemplateInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='documentTemplate' type='documentTemplate'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -0,0 +1,83 @@
import { useRef } from 'react'
import { Flex, Space } from 'antd'
import ObjectTable from '../common/ObjectTable'
import ObjectActions from '../common/ObjectActions'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import SortSidebarButton from '../common/SortSidebarButton'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import ListViewTabs from '../common/ListViewTabs'
import ListViewEditButtons from '../common/ListViewEditButtons'
import { ObjectListViewProvider } from '../context/ObjectListViewContext'
const EmailAccounts = () => {
const tableRef = useRef()
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('emailAccount')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('EmailAccounts')
const [showSortSidebar, setShowSortSidebar] =
useSortSidebarVisibility('EmailAccounts')
return (
<ObjectListViewProvider objectType='emailAccount' tableRef={tableRef}>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify='space-between' style={{ minHeight: 0 }} gap='small'>
<Space size='small'>
<ObjectActions
type='emailAccount'
pageName='list'
onReload={() => tableRef.current?.reload()}
/>
<ColumnViewButton
type='emailAccount'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='emailAccount' />
</Space>
<ListViewTabs />
<Space>
<SortSidebarButton
active={showSortSidebar}
onClick={() => setShowSortSidebar(!showSortSidebar)}
/>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton objectType='emailAccount' />
<ListViewEditButtons showStartingDivider={true} />
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='emailAccount'
showFilterSidebar={showFilterSidebar}
showSortSidebar={showSortSidebar}
expandHeight={true}
saveFilterInSession={true}
saveFilterInUrl={true}
useFilterInSession={true}
useFilterInUrl={true}
saveSortInSession={true}
saveSortInUrl={true}
useSortInSession={true}
useSortInUrl={true}
/>
</Flex>
</ObjectListViewProvider>
)
}
export default EmailAccounts

View File

@ -0,0 +1,238 @@
import { useRef, useState, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.jsx'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('EmailAccountInfo')
log.setLevel(config.logLevel)
const EmailAccountInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const emailAccountId = new URLSearchParams(location.search).get(
'emailAccountId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'EmailAccountInfo',
{
info: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
activities: [],
loading: false,
editDisabled: false,
objectData: {}
})
const currentObjectTools = useMemo(
() => (
<ObjectTableNavigationButtons
disabled={objectFormState.loading || objectFormState.isEditing}
_id={emailAccountId}
objectType='emailAccount'
showEndingDivider={false}
/>
),
[objectFormState.loading, objectFormState.isEditing, emailAccountId]
)
useDashboardObjectTools(currentObjectTools)
const actions = {
edit: () => {
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
objectFormRef?.current.handleUpdate()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='small'>
<Space size='small'>
<ObjectActions
type='emailAccount'
pageName='info'
id={emailAccountId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Email Account Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='emailAccount'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<InfoActionButtons
type='emailAccount'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<ActivityIndicator
activities={objectFormState.activities}
showStartingDivider={true}
/>
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={
objectFormState.beingEditedByOther ||
objectFormState.loading ||
objectFormState.editDisabled
}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Email Account Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={emailAccountId}
type='emailAccount'
style={{ height: '100%' }}
ref={objectFormRef}
setCurrentObject={true}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='emailAccount'
objectData={objectData}
labelWidth='190px'
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Spin
spinning={objectFormState.loading}
indicator={<LoadingOutlined />}
>
<Card>
<NotesPanel _id={emailAccountId} type='emailAccount' />
</Card>
</Spin>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{
parent:
getModelByName('emailAccount').prefix +
':' +
emailAccountId
}}
visibleColumns={{ _id: false, parent: false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default EmailAccountInfo

View File

@ -0,0 +1,95 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewEmailAccount = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'emailAccount'}
defaultValues={{
active: true,
port: 587,
secure: false,
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='emailAccount'
column={1}
bordered={false}
isEditing={true}
required={true}
labelWidth={130}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='emailAccount'
column={1}
bordered={false}
isEditing={true}
required={false}
labelWidth={130}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='emailAccount'
column={1}
bordered={false}
visibleProperties={{
_id: false,
_reference: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Email Account'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewEmailAccount.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewEmailAccount

View File

@ -0,0 +1,83 @@
import { useRef } from 'react'
import { Flex, Space } from 'antd'
import ObjectTable from '../common/ObjectTable'
import ObjectActions from '../common/ObjectActions'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import SortSidebarButton from '../common/SortSidebarButton'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import ListViewTabs from '../common/ListViewTabs'
import ListViewEditButtons from '../common/ListViewEditButtons'
import { ObjectListViewProvider } from '../context/ObjectListViewContext'
const EmailMessages = () => {
const tableRef = useRef()
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('emailMessage')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('EmailMessages')
const [showSortSidebar, setShowSortSidebar] =
useSortSidebarVisibility('EmailMessages')
return (
<ObjectListViewProvider objectType='emailMessage' tableRef={tableRef}>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify='space-between' style={{ minHeight: 0 }} gap='small'>
<Space size='small'>
<ObjectActions
type='emailMessage'
pageName='list'
onReload={() => tableRef.current?.reload()}
/>
<ColumnViewButton
type='emailMessage'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='emailMessage' />
</Space>
<ListViewTabs />
<Space>
<SortSidebarButton
active={showSortSidebar}
onClick={() => setShowSortSidebar(!showSortSidebar)}
/>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton objectType='emailMessage' />
<ListViewEditButtons showStartingDivider={true} />
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='emailMessage'
showFilterSidebar={showFilterSidebar}
showSortSidebar={showSortSidebar}
expandHeight={true}
saveFilterInSession={true}
saveFilterInUrl={true}
useFilterInSession={true}
useFilterInUrl={true}
saveSortInSession={true}
saveSortInUrl={true}
useSortInSession={true}
useSortInUrl={true}
/>
</Flex>
</ObjectListViewProvider>
)
}
export default EmailMessages

View File

@ -0,0 +1,227 @@
import { useRef, useState, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.jsx'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
import TemplatePreview from '../../common/TemplatePreview.jsx'
const log = loglevel.getLogger('EmailMessageInfo')
log.setLevel(config.logLevel)
const EmailMessageInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const emailMessageId = new URLSearchParams(location.search).get(
'emailMessageId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'EmailMessageInfo',
{
info: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
activities: [],
loading: false,
editDisabled: false,
objectData: {}
})
const currentObjectTools = useMemo(
() => (
<ObjectTableNavigationButtons
disabled={objectFormState.loading}
_id={emailMessageId}
objectType='emailMessage'
showEndingDivider={false}
/>
),
[objectFormState.loading, emailMessageId]
)
useDashboardObjectTools(currentObjectTools)
return (
<>
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='small'>
<Space size='small'>
<ObjectActions
type='emailMessage'
pageName='info'
id={emailMessageId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Email Message Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='emailMessage'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<InfoActionButtons
type='emailMessage'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<ActivityIndicator
activities={objectFormState.activities}
showStartingDivider={true}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ObjectForm
id={emailMessageId}
type='emailMessage'
style={{ height: '100%' }}
ref={objectFormRef}
setCurrentObject={true}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Email Message Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='emailMessage'
objectData={objectData}
labelWidth='190px'
visibleProperties={{
content: false
}}
/>
</InfoCollapse>
<InfoCollapse
title='Email Message Content'
icon={<InfoCircleIcon />}
active={collapseState.content}
onToggle={(expanded) =>
updateCollapseState('content', expanded)
}
collapseKey='content'
>
<Spin
spinning={objectFormState.loading}
indicator={<LoadingOutlined />}
>
<Card>
<TemplatePreview
template={objectData?.template}
content={objectData?.content}
templateType='emailMessage'
capabilities={{
htmlOnly: true,
widthControl: true,
defaultWidth: 600
}}
/>
</Card>
</Spin>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Spin
spinning={objectFormState.loading}
indicator={<LoadingOutlined />}
>
<Card>
<NotesPanel _id={emailMessageId} type='emailMessage' />
</Card>
</Spin>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{
parent:
getModelByName('emailMessage').prefix +
':' +
emailMessageId
}}
visibleColumns={{ _id: false, parent: false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default EmailMessageInfo

View File

@ -0,0 +1,319 @@
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

View File

@ -0,0 +1,83 @@
import { useRef } from 'react'
import { Flex, Space } from 'antd'
import ObjectTable from '../common/ObjectTable'
import ObjectActions from '../common/ObjectActions'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import SortSidebarButton from '../common/SortSidebarButton'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import ListViewTabs from '../common/ListViewTabs'
import ListViewEditButtons from '../common/ListViewEditButtons'
import { ObjectListViewProvider } from '../context/ObjectListViewContext'
const EmailTemplates = () => {
const tableRef = useRef()
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('emailTemplate')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('EmailTemplates')
const [showSortSidebar, setShowSortSidebar] =
useSortSidebarVisibility('EmailTemplates')
return (
<ObjectListViewProvider objectType='emailTemplate' tableRef={tableRef}>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify='space-between' style={{ minHeight: 0 }} gap='small'>
<Space size='small'>
<ObjectActions
type='emailTemplate'
pageName='list'
onReload={() => tableRef.current?.reload()}
/>
<ColumnViewButton
type='emailTemplate'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='emailTemplate' />
</Space>
<ListViewTabs />
<Space>
<SortSidebarButton
active={showSortSidebar}
onClick={() => setShowSortSidebar(!showSortSidebar)}
/>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton objectType='emailTemplate' />
<ListViewEditButtons showStartingDivider={true} />
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='emailTemplate'
showFilterSidebar={showFilterSidebar}
showSortSidebar={showSortSidebar}
expandHeight={true}
saveFilterInSession={true}
saveFilterInUrl={true}
useFilterInSession={true}
useFilterInUrl={true}
saveSortInSession={true}
saveSortInUrl={true}
useSortInSession={true}
useSortInUrl={true}
/>
</Flex>
</ObjectListViewProvider>
)
}
export default EmailTemplates

View File

@ -0,0 +1,159 @@
import { useMemo, useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ViewButton from '../../common/ViewButton'
import NoteIcon from '../../../Icons/NoteIcon'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons'
import ActivityIndicator from '../../common/ActivityIndicator'
import ActionHandler from '../../common/ActionHandler'
import ObjectActions from '../../common/ObjectActions'
import TemplateEditor from '../../common/TemplateEditor'
import ScrollBox from '../../common/ScrollBox'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
const EmailTemplateDesign = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const emailTemplateId = new URLSearchParams(location.search).get(
'emailTemplateId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'EmailTemplateDesign',
{ preview: true, editor: true, notes: true }
)
const [formState, setFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
activities: [],
loading: false,
objectData: {}
})
useDashboardObjectTools(
useMemo(
() => (
<ObjectTableNavigationButtons
disabled={formState.loading || formState.isEditing}
_id={emailTemplateId}
objectType='emailTemplate'
showEndingDivider={false}
/>
),
[emailTemplateId, formState.isEditing, formState.loading]
)
)
return (
<Flex gap='large' vertical style={{ maxHeight: '100%', minHeight: 0 }}>
<Flex justify='space-between'>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='emailTemplate'
pageName='info'
id={emailTemplateId}
disabled={formState.loading}
visibleActions={{ edit: false }}
/>
<ViewButton
disabled={formState.loading}
items={[
{ key: 'preview', label: 'Preview' },
{ key: 'editor', label: 'Editor' },
{ key: 'notes', label: 'Notes' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<ActivityIndicator activities={formState.activities} />
</Space>
<EditButtons
isEditing={formState.isEditing}
handleUpdate={() => actionHandlerRef.current.callAction('finishEdit')}
cancelEditing={() =>
actionHandlerRef.current.callAction('cancelEdit')
}
startEditing={() => actionHandlerRef.current.callAction('edit')}
editLoading={formState.editLoading}
formValid={formState.formValid}
disabled={formState.beingEditedByOther || formState.loading}
loading={formState.editLoading}
/>
</Flex>
<ScrollBox>
<Flex vertical gap='large'>
<ActionHandler
actions={{
edit: () => {
objectFormRef.current?.startEditing()
return false
},
cancelEdit: () => {
objectFormRef.current?.cancelEditing()
return true
},
finishEdit: () => {
objectFormRef.current?.handleUpdate()
return true
}
}}
loading={formState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={emailTemplateId}
type='emailTemplate'
ref={objectFormRef}
onStateChange={(state) =>
setFormState((previous) => ({ ...previous, ...state }))
}
>
{({ loading, isEditing, objectData, form, setObjectData }) => (
<TemplateEditor
objectData={objectData}
templateType='emailTemplate'
subjectProperty='subject'
previewCapabilities={{
htmlOnly: true,
widthControl: true,
defaultWidth: 600
}}
loading={loading}
style={{ height: '72vh' }}
collapseState={collapseState}
isEditing={isEditing}
form={form}
setObjectData={setObjectData}
/>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Spin spinning={formState.loading} indicator={<LoadingOutlined />}>
<Card>
<NotesPanel _id={emailTemplateId} type='emailTemplate' />
</Card>
</Spin>
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default EmailTemplateDesign

View File

@ -0,0 +1,269 @@
import { useRef, useState, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import { Card, Flex, Space, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.jsx'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import FileIcon from '../../../Icons/FileIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName, getModelProperty } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('EmailTemplateInfo')
log.setLevel(config.logLevel)
const EmailTemplateInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const emailTemplateId = new URLSearchParams(location.search).get(
'emailTemplateId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'EmailTemplateInfo',
{
info: true,
attachments: true,
notes: true,
auditLogs: false
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
activities: [],
loading: false,
editDisabled: false,
objectData: {}
})
const currentObjectTools = useMemo(
() => (
<ObjectTableNavigationButtons
disabled={objectFormState.loading || objectFormState.isEditing}
_id={emailTemplateId}
objectType='emailTemplate'
showEndingDivider={false}
/>
),
[objectFormState.loading, objectFormState.isEditing, emailTemplateId]
)
useDashboardObjectTools(currentObjectTools)
const actions = {
edit: () => {
objectFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
objectFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
objectFormRef?.current.handleUpdate()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='small'>
<Space size='small'>
<ObjectActions
type='emailTemplate'
pageName='info'
id={emailTemplateId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Email Template Information' },
{ key: 'attachments', label: 'Attachments' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='emailTemplate'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<InfoActionButtons
type='emailTemplate'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<ActivityIndicator
activities={objectFormState.activities}
showStartingDivider={true}
/>
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={
objectFormState.beingEditedByOther ||
objectFormState.loading ||
objectFormState.editDisabled
}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={emailTemplateId}
type='emailTemplate'
style={{ height: '100%' }}
ref={objectFormRef}
setCurrentObject={true}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Email Template Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='emailTemplate'
objectData={objectData}
visibleProperties={{
content: false,
testObject: false,
attachments: false
}}
labelWidth='210px'
/>
</InfoCollapse>
<InfoCollapse
title='Attachments'
icon={<FileIcon />}
active={collapseState.attachments}
onToggle={(expanded) =>
updateCollapseState('attachments', expanded)
}
collapseKey='attachments'
>
<ObjectProperty
{...getModelProperty('emailTemplate', 'attachments')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Spin
spinning={objectFormState.loading}
indicator={<LoadingOutlined />}
>
<Card>
<NotesPanel _id={emailTemplateId} type='emailTemplate' />
</Card>
</Spin>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{
parent:
getModelByName('emailTemplate').prefix +
':' +
emailTemplateId
}}
visibleColumns={{ _id: false, parent: false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default EmailTemplateInfo

View File

@ -0,0 +1,97 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewEmailTemplate = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'emailTemplate'}
defaultValues={{ active: true, global: false, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='emailTemplate'
column={1}
bordered={false}
isEditing={true}
required={true}
labelWidth={110}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='emailTemplate'
column={1}
bordered={false}
isEditing={true}
required={false}
visibleProperties={{
content: false,
testObject: false,
referencedTemplates: false,
attachments: false
}}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='emailTemplate'
column={1}
bordered={false}
visibleProperties={{
_id: false,
_reference: false,
createdAt: false,
updatedAt: false,
referencedTemplates: false,
attachments: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Email Template'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewEmailTemplate.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewEmailTemplate

View File

@ -19,7 +19,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -109,7 +109,7 @@ const FilamentSkuInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='filamentSku' type='filamentSku'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -24,7 +24,7 @@ import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import FilamentIcon from '../../../Icons/FilamentIcon.jsx' import FilamentIcon from '../../../Icons/FilamentIcon.jsx'
import FilamentSkuIcon from '../../../Icons/FilamentSkuIcon.jsx' import FilamentSkuIcon from '../../../Icons/FilamentSkuIcon.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
@ -131,7 +131,7 @@ const FilamentInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='filament' type='filament'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import FileIcon from '../../../Icons/FileIcon.jsx' import FileIcon from '../../../Icons/FileIcon.jsx'
import FilePreview from '../../common/FilePreview.jsx' import FilePreview from '../../common/FilePreview.jsx'
@ -124,7 +124,7 @@ const FileInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='file' type='file'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import PrinterIcon from '../../../Icons/PrinterIcon.jsx' import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
import DocumentPrinterIcon from '../../../Icons/DocumentPrinterIcon.jsx' import DocumentPrinterIcon from '../../../Icons/DocumentPrinterIcon.jsx'
@ -126,7 +126,7 @@ const HostInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='host' type='host'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -112,7 +112,7 @@ const MaterialInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='material' type='material'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -17,7 +17,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -103,7 +103,7 @@ const NoteTypeInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='noteType' type='noteType'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
@ -113,7 +113,7 @@ const NoteInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='note' type='note'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -19,7 +19,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -104,7 +104,7 @@ const PartSkuInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='partSku' type='partSku'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -20,7 +20,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
@ -116,7 +116,7 @@ const PartInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='part' type='part'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -19,7 +19,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { import {
getModelByName, getModelByName,
@ -115,7 +115,7 @@ const PermissionSettingInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='permissionSetting' type='permissionSetting'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -113,7 +113,7 @@ const ProductCategoryInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='productCategory' type='productCategory'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -19,7 +19,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx' import ObjectProperty from '../../common/ObjectProperty.jsx'
@ -111,7 +111,7 @@ const ProductSkuInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='productSku' type='productSku'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -20,7 +20,7 @@ import { useActions } from '../../context/ActionsContext'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
@ -116,7 +116,7 @@ const ProductInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='product' type='product'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -20,7 +20,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -110,7 +110,7 @@ const StockAuditLevelInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='stockAuditLevel' type='stockAuditLevel'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -113,7 +113,7 @@ const TaxRateInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='taxRate' type='taxRate'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -19,7 +19,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx' import ObjectProperty from '../../common/ObjectProperty.jsx'
@ -115,7 +115,7 @@ const UserGroupInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='userGroup' type='userGroup'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -20,7 +20,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx' import ObjectProperty from '../../common/ObjectProperty.jsx'
@ -114,7 +114,7 @@ const UserInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='user' type='user'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -109,7 +109,7 @@ const VendorInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='vendor' type='vendor'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -4,7 +4,7 @@ import { Card, Flex, Space, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import ActionHandler from '../../common/ActionHandler' import ActionHandler from '../../common/ActionHandler'
import AuditLogIcon from '../../../Icons/AuditLogIcon' import AuditLogIcon from '../../../Icons/AuditLogIcon'
import DocumentPrintButton from '../../common/DocumentPrintButton' import InfoActionButtons from '../../common/InfoActionButtons'
import EditButtons from '../../common/EditButtons' import EditButtons from '../../common/EditButtons'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx' import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import FilamentIcon from '../../../Icons/FilamentIcon' import FilamentIcon from '../../../Icons/FilamentIcon'
@ -158,7 +158,7 @@ const FilamentProfileInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='filamentProfile' type='filamentProfile'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
@ -120,7 +120,7 @@ const GCodeFileInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='gcodeFile' type='gcodeFile'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import JobIcon from '../../../Icons/JobIcon.jsx' import JobIcon from '../../../Icons/JobIcon.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -126,7 +126,7 @@ const JobInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='job' type='job'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -4,7 +4,7 @@ import { Card, Flex, Space, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import ActionHandler from '../../common/ActionHandler' import ActionHandler from '../../common/ActionHandler'
import AuditLogIcon from '../../../Icons/AuditLogIcon' import AuditLogIcon from '../../../Icons/AuditLogIcon'
import DocumentPrintButton from '../../common/DocumentPrintButton' import InfoActionButtons from '../../common/InfoActionButtons'
import EditButtons from '../../common/EditButtons' import EditButtons from '../../common/EditButtons'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx' import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import HotEndIcon from '../../../Icons/HotEndIcon' import HotEndIcon from '../../../Icons/HotEndIcon'
@ -130,7 +130,7 @@ const PrinterProfileInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='printerProfile' type='printerProfile'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import PrinterProfileIcon from '../../../Icons/PrinterProfileIcon.jsx' import PrinterProfileIcon from '../../../Icons/PrinterProfileIcon.jsx'
@ -127,7 +127,7 @@ const PrinterInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='printer' type='printer'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -19,7 +19,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
@ -104,7 +104,7 @@ const SubJobInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='subJob' type='subJob'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -21,7 +21,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
@ -109,7 +109,7 @@ const ClientInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='client' type='client'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { import {
getModelProperty, getModelProperty,
@ -121,7 +121,7 @@ const FulfillmentPolicyInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='fulfillmentPolicy' type='fulfillmentPolicy'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -25,7 +25,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { import {
@ -132,7 +132,7 @@ const ListingVarientInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='listingVarient' type='listingVarient'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -26,7 +26,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { import {
getModelByName, getModelByName,
@ -131,7 +131,7 @@ const ListingInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='listing' type='listing'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { ApiServerContext } from '../../context/ApiServerContext.jsx' import { ApiServerContext } from '../../context/ApiServerContext.jsx'
@ -189,7 +189,7 @@ const MarketplaceInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='marketplace' type='marketplace'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -23,7 +23,7 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext' import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { import {
getModelProperty, getModelProperty,
@ -121,7 +121,7 @@ const ReturnPolicyInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='returnPolicy' type='returnPolicy'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -22,7 +22,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import InfoActionButtons from '../../common/InfoActionButtons.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx' import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
@ -141,7 +141,7 @@ const SalesOrderInfo = () => {
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}
/> />
<DocumentPrintButton <InfoActionButtons
type='salesOrder' type='salesOrder'
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
disabled={objectFormState.loading} disabled={objectFormState.loading}

View File

@ -0,0 +1,215 @@
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

View File

@ -0,0 +1,19 @@
import { Flex } from 'antd'
import PropTypes from 'prop-types'
import DocumentPrintButton from './DocumentPrintButton'
import EmailSendButton from './EmailSendButton'
const InfoActionButtons = (props) => (
<Flex gap='small'>
<DocumentPrintButton {...props} />
<EmailSendButton {...props} />
</Flex>
)
InfoActionButtons.propTypes = {
type: PropTypes.string.isRequired,
disabled: PropTypes.bool,
objectData: PropTypes.object.isRequired
}
export default InfoActionButtons

View File

@ -967,6 +967,17 @@ const ObjectProperty = ({
) )
case 'text': case 'text':
return <Input placeholder={label} {...inputProps} /> return <Input placeholder={label} {...inputProps} />
case 'email':
if (Array.isArray(options) && options.length) {
return (
<CustomSelect
placeholder={'Select a ' + label.toLowerCase() + '...'}
options={options}
{...inputProps}
/>
)
}
return <Input type='email' placeholder={label} {...inputProps} />
case 'codeBlock': case 'codeBlock':
return ( return (
<CodeBlockEditor <CodeBlockEditor

View File

@ -96,6 +96,11 @@ const parseEjsErrorLines = (message) => {
const TemplateEditor = ({ const TemplateEditor = ({
objectData, objectData,
template,
templateType = 'documentTemplate',
editorLanguage = 'fcTemplateLang',
subjectProperty,
previewCapabilities,
loading, loading,
collapseState, collapseState,
isEditing, isEditing,
@ -104,6 +109,7 @@ const TemplateEditor = ({
setObjectData setObjectData
}) => { }) => {
const { fetchTemplateFormat } = useContext(ApiServerContext) const { fetchTemplateFormat } = useContext(ApiServerContext)
const currentTemplate = template || objectData
const [testObjectOpen, setTestObjectOpen] = useState(false) const [testObjectOpen, setTestObjectOpen] = useState(false)
const [testObjectViewMode, setTestObjectViewMode] = useState('Tree') const [testObjectViewMode, setTestObjectViewMode] = useState('Tree')
const [previewMessage, setPreviewMessage] = useState('No issues found.') const [previewMessage, setPreviewMessage] = useState('No issues found.')
@ -150,14 +156,14 @@ const TemplateEditor = ({
}, []) }, [])
const handleFormatCode = useCallback(async () => { const handleFormatCode = useCallback(async () => {
const content = form?.getFieldValue?.('content') ?? objectData?.content const content = form?.getFieldValue?.('content') ?? currentTemplate?.content
if (content == null || typeof content !== 'string') { if (content == null || typeof content !== 'string') {
return return
} }
setFormatLoading(true) setFormatLoading(true)
try { try {
const result = await fetchTemplateFormat(content) const result = await fetchTemplateFormat(content, null, templateType)
if (result?.error) { if (result?.error) {
handlePreviewMessage(result.error, true) handlePreviewMessage(result.error, true)
return return
@ -177,10 +183,11 @@ const TemplateEditor = ({
} }
}, [ }, [
form, form,
objectData?.content, currentTemplate?.content,
fetchTemplateFormat, fetchTemplateFormat,
handlePreviewMessage, handlePreviewMessage,
setObjectData setObjectData,
templateType
]) ])
return ( return (
@ -195,8 +202,10 @@ const TemplateEditor = ({
styles={{ body: { height: '100%', padding: '22px' } }} styles={{ body: { height: '100%', padding: '22px' } }}
> >
<TemplatePreview <TemplatePreview
objectData={objectData?.testObject} objectData={currentTemplate?.testObject}
documentTemplate={objectData} template={currentTemplate}
templateType={templateType}
capabilities={previewCapabilities}
loading={loading} loading={loading}
isEditing={isEditing} isEditing={isEditing}
style={style} style={style}
@ -244,6 +253,24 @@ const TemplateEditor = ({
)} )}
</Flex> </Flex>
{subjectProperty ? (
<ObjectProperty
useFormItem={false}
name={subjectProperty}
type='text'
objectData={currentTemplate}
value={currentTemplate?.[subjectProperty]}
isEditing={isEditing}
onChange={(value) => {
form?.setFieldValue?.(subjectProperty, value)
setObjectData?.((previous) => ({
...previous,
[subjectProperty]: value
}))
}}
/>
) : null}
<div <div
style={{ style={{
width: '100%', width: '100%',
@ -264,8 +291,8 @@ const TemplateEditor = ({
name={'content'} name={'content'}
height='100%' height='100%'
type='codeBlock' type='codeBlock'
language={'fcTemplateLang'} language={editorLanguage}
objectData={objectData} objectData={currentTemplate}
onChange={(value) => { onChange={(value) => {
form?.setFieldValue?.('content', value) form?.setFieldValue?.('content', value)
setObjectData?.((prev) => ({ setObjectData?.((prev) => ({
@ -274,7 +301,7 @@ const TemplateEditor = ({
})) }))
}} }}
isEditing={isEditing} isEditing={isEditing}
autoCompleteObject={objectData?.testObject} autoCompleteObject={currentTemplate?.testObject}
errorLines={errorLines} errorLines={errorLines}
/> />
</div> </div>
@ -318,13 +345,13 @@ const TemplateEditor = ({
type={'codeBlock'} type={'codeBlock'}
name='testObject' name='testObject'
language='json' language='json'
objectData={objectData} objectData={currentTemplate}
isEditing={true} isEditing={true}
/> />
)} )}
{testObjectViewMode == 'Tree' && ( {testObjectViewMode == 'Tree' && (
<DataTree <DataTree
data={objectData?.testObject} data={currentTemplate?.testObject}
defaultExpandAll={true} defaultExpandAll={true}
showValueCopy={false} showValueCopy={false}
showKeyCopy={true} showKeyCopy={true}
@ -340,6 +367,11 @@ const TemplateEditor = ({
TemplateEditor.propTypes = { TemplateEditor.propTypes = {
loading: PropTypes.bool, loading: PropTypes.bool,
objectData: PropTypes.object, objectData: PropTypes.object,
template: PropTypes.object,
templateType: PropTypes.string,
editorLanguage: PropTypes.string,
subjectProperty: PropTypes.string,
previewCapabilities: PropTypes.object,
collapseState: PropTypes.object, collapseState: PropTypes.object,
isEditing: PropTypes.bool, isEditing: PropTypes.bool,
style: PropTypes.object, style: PropTypes.object,

View File

@ -7,7 +7,7 @@ import {
useCallback useCallback
} from 'react' } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Flex, Button, Input, Select, Card, Popover } from 'antd' import { Flex, Button, Input, InputNumber, Select, Card, Popover } from 'antd'
import PlusIcon from '../../Icons/PlusIcon.jsx' import PlusIcon from '../../Icons/PlusIcon.jsx'
import MinusIcon from '../../Icons/MinusIcon.jsx' import MinusIcon from '../../Icons/MinusIcon.jsx'
import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx' import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
@ -24,6 +24,10 @@ const noop = () => {}
const TemplatePreview = ({ const TemplatePreview = ({
objectData, objectData,
documentTemplate, documentTemplate,
content = null,
template,
templateType = 'documentTemplate',
capabilities = {},
loading, loading,
isEditing, isEditing,
onTestObjectOpen = noop, onTestObjectOpen = noop,
@ -31,6 +35,10 @@ const TemplatePreview = ({
showTestObject = false, showTestObject = false,
showPreviewSwitch = true showPreviewSwitch = true
}) => { }) => {
const currentTemplate = template || documentTemplate
const htmlOnly = capabilities.htmlOnly === true
const showWidthControl = capabilities.widthControl === true
const defaultPreviewWidth = capabilities.defaultWidth || 600
const iframeRef = useRef(null) const iframeRef = useRef(null)
const scrollElementRef = useRef(null) const scrollElementRef = useRef(null)
const savedScrollRef = useRef({ top: 0, left: 0 }) const savedScrollRef = useRef({ top: 0, left: 0 })
@ -48,6 +56,7 @@ const TemplatePreview = ({
const [downloadProgress, setDownloadProgress] = useState(null) const [downloadProgress, setDownloadProgress] = useState(null)
const [previewScale, setPreviewScale] = useState(1) const [previewScale, setPreviewScale] = useState(1)
const [previewType, setPreviewType] = useState('HTML') const [previewType, setPreviewType] = useState('HTML')
const [previewWidth, setPreviewWidth] = useState(defaultPreviewWidth)
const [panMode, setPanMode] = useState(false) const [panMode, setPanMode] = useState(false)
const [isPanning, setIsPanning] = useState(false) const [isPanning, setIsPanning] = useState(false)
const panStartRef = useRef({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 }) const panStartRef = useRef({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 })
@ -121,8 +130,12 @@ const TemplatePreview = ({
parseFloat(containerStyle.paddingBottom) parseFloat(containerStyle.paddingBottom)
} }
const contentWidth = Math.ceil(visual.width + padX) const contentWidth = showWidthControl
const contentHeight = Math.ceil(visual.height + padY) ? previewWidth
: Math.ceil(visual.width + padX)
const contentHeight = Math.ceil(
Math.max(visual.height + padY, doc.documentElement.scrollHeight)
)
if (!doc.head?.querySelector('style[data-preview-outer-scroll]')) { if (!doc.head?.querySelector('style[data-preview-outer-scroll]')) {
const styleEl = doc.createElement('style') const styleEl = doc.createElement('style')
@ -140,7 +153,7 @@ const TemplatePreview = ({
} }
return { width, height } return { width, height }
}) })
}, []) }, [previewWidth, showWidthControl])
const restoreScrollPosition = useCallback(() => { const restoreScrollPosition = useCallback(() => {
const scrollEl = scrollElementRef.current const scrollEl = scrollElementRef.current
@ -200,8 +213,11 @@ const TemplatePreview = ({
} }
setPreviewContentHTML('') setPreviewContentHTML('')
setIframeSize({ width: '100%', height: 'auto' }) setIframeSize({
}, []) width: showWidthControl ? `${previewWidth}px` : '100%',
height: 'auto'
})
}, [previewWidth, showWidthControl])
const stopRenderProgressListener = useCallback(() => { const stopRenderProgressListener = useCallback(() => {
if (typeof renderUnsubscribeRef.current === 'function') { if (typeof renderUnsubscribeRef.current === 'function') {
@ -234,7 +250,7 @@ const TemplatePreview = ({
const reloadPreviewPDF = useCallback( const reloadPreviewPDF = useCallback(
(content, testObject = {}) => { (content, testObject = {}) => {
const templateId = documentTemplate?._id const templateId = currentTemplate?._id
if (!templateId) { if (!templateId) {
return return
} }
@ -346,7 +362,7 @@ const TemplatePreview = ({
}, },
[ [
applyPdfResult, applyPdfResult,
documentTemplate?._id, currentTemplate?._id,
fetchTemplateDownload, fetchTemplateDownload,
stopRenderProgressListener, stopRenderProgressListener,
subscribeToObjectEvent subscribeToObjectEvent
@ -355,39 +371,61 @@ const TemplatePreview = ({
const reloadPreview = useCallback( const reloadPreview = useCallback(
(content, testObject = {}, scale = 1) => { (content, testObject = {}, scale = 1) => {
const templateId = documentTemplate?._id const templateId = currentTemplate?._id
if (!templateId) { if (!templateId) {
return return
} }
const requestId = ++previewRequestIdRef.current const requestId = ++previewRequestIdRef.current
setReloadLoading(true) setReloadLoading(true)
fetchTemplatePreview(templateId, content, testObject, scale, (result) => { fetchTemplatePreview(
if (requestId !== previewRequestIdRef.current) { templateId,
return content,
} testObject,
setReloadLoading(false) scale,
if (result?.error) { (result) => {
onPreviewMessage(result.error, true) if (requestId !== previewRequestIdRef.current) {
} else { return
updatePreviewContentHTML(result.html) }
onPreviewMessage('No issues found.', false) setReloadLoading(false)
} if (result?.error) {
}) onPreviewMessage(result.error, true)
} else {
updatePreviewContentHTML(result.html)
onPreviewMessage('No issues found.', false)
}
},
templateType,
showWidthControl ? { width: previewWidth } : {}
)
}, },
[fetchTemplatePreview, onPreviewMessage, documentTemplate?._id] [
currentTemplate?._id,
fetchTemplatePreview,
onPreviewMessage,
previewWidth,
showWidthControl,
templateType
]
) )
const templateContent = documentTemplate?.content const templateContent = currentTemplate?.content
const templateId = documentTemplate?._id const templateId = currentTemplate?._id
useEffect(() => { useEffect(() => {
if (content != null) {
updatePreviewContentHTML(content)
return
}
if (!templateId || !templateContent || previewType != 'HTML') { if (!templateId || !templateContent || previewType != 'HTML') {
return return
} }
const timeoutId = window.setTimeout(() => { const timeoutId = window.setTimeout(() => {
reloadPreview(templateContent, objectData, previewScale) if (content == null) {
reloadPreview(templateContent, objectData, previewScale)
}
}, 300) }, 300)
return () => { return () => {
@ -398,8 +436,10 @@ const TemplatePreview = ({
templateContent, templateContent,
templateId, templateId,
previewScale, previewScale,
previewWidth,
previewType, previewType,
reloadPreview reloadPreview,
content
]) ])
useEffect(() => { useEffect(() => {
@ -534,7 +574,20 @@ const TemplatePreview = ({
}} }}
disabled={loading || reloadLoading} disabled={loading || reloadLoading}
/> />
{showPreviewSwitch == true ? ( {showWidthControl ? (
<InputNumber
min={240}
max={1600}
step={10}
addonAfter='px'
value={previewWidth}
disabled={loading || reloadLoading}
onChange={(value) => setPreviewWidth(value || defaultPreviewWidth)}
style={{ width: 130 }}
aria-label='Email preview width'
/>
) : null}
{showPreviewSwitch == true && !htmlOnly ? (
<Select <Select
options={[ options={[
{ value: 'HTML', label: 'HTML' }, { value: 'HTML', label: 'HTML' },
@ -561,13 +614,13 @@ const TemplatePreview = ({
<Flex gap={'small'} ref={toolbarRef}> <Flex gap={'small'} ref={toolbarRef}>
{showTestObject == true ? ( {showTestObject == true ? (
<> <>
{documentTemplate?.objectType ? ( {currentTemplate?.objectType ? (
<ObjectProperty <ObjectProperty
objectType={documentTemplate?.objectType} objectType={currentTemplate?.objectType}
name={'testObject'} name={'testObject'}
isEditing={true} isEditing={true}
objectData={documentTemplate} objectData={currentTemplate}
disabled={!isEditing || documentTemplate?.global} disabled={!isEditing || currentTemplate?.global}
type={'object'} type={'object'}
style={{ minWidth: 0 }} style={{ minWidth: 0 }}
/> />
@ -578,7 +631,7 @@ const TemplatePreview = ({
)} )}
<Button <Button
icon={<InfoCircleIcon />} icon={<InfoCircleIcon />}
disabled={documentTemplate?.global} disabled={currentTemplate?.global}
onClick={() => { onClick={() => {
onTestObjectOpen() onTestObjectOpen()
}} }}
@ -707,12 +760,16 @@ TemplatePreview.propTypes = {
loading: PropTypes.bool, loading: PropTypes.bool,
objectData: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), objectData: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
documentTemplate: PropTypes.object, documentTemplate: PropTypes.object,
template: PropTypes.object,
templateType: PropTypes.string,
capabilities: PropTypes.object,
isEditing: PropTypes.bool, isEditing: PropTypes.bool,
style: PropTypes.object, style: PropTypes.object,
showTestObject: PropTypes.bool, showTestObject: PropTypes.bool,
onTestObjectOpen: PropTypes.func, onTestObjectOpen: PropTypes.func,
onPreviewMessage: PropTypes.func, onPreviewMessage: PropTypes.func,
showPreviewSwitch: PropTypes.bool showPreviewSwitch: PropTypes.bool,
content: PropTypes.string
} }
export default TemplatePreview export default TemplatePreview

View File

@ -2105,11 +2105,15 @@ const ApiServerProvider = ({ children }) => {
URL.revokeObjectURL(fileUrl) URL.revokeObjectURL(fileUrl)
} }
const fetchTemplateFormat = async (content, callback) => { const fetchTemplateFormat = async (
content,
callback,
templateType = 'documentTemplate'
) => {
logger.debug('Formatting template content...') logger.debug('Formatting template content...')
try { try {
const response = await axios.post( const response = await axios.post(
`${config.backendUrl}/documenttemplates/format`, `${config.backendUrl}/${getObjectEndpoint(templateType)}/format`,
{ content }, { content },
{ {
headers: { headers: {
@ -2137,17 +2141,20 @@ const ApiServerProvider = ({ children }) => {
content, content,
testObject, testObject,
scale, scale,
callback callback,
templateType = 'documentTemplate',
previewOptions = {}
) => { ) => {
logger.debug('Fetching preview...') logger.debug('Fetching preview...')
try { try {
const response = await axios.post( const response = await axios.post(
`${config.backendUrl}/documenttemplates/${id}/preview`, `${config.backendUrl}/${getObjectEndpoint(templateType)}/${id}/preview`,
{ {
content, content,
testObject, testObject,
scale, scale,
padding: true ...(templateType === 'documentTemplate' ? { padding: true } : {}),
...previewOptions
}, },
{ {
headers: { headers: {

View File

@ -289,6 +289,18 @@ export function getStateTagInfo(state) {
status = 'error' status = 'error'
text = 'Overdue' text = 'Overdue'
break break
case 'rendering':
status = 'volcano'
text = 'Rendering'
break
case 'sending':
status = 'processing'
text = 'Sending'
break
case 'read':
status = 'success'
text = 'Read'
break
default: default:
status = 'default' status = 'default'
text = state || 'Unknown' text = state || 'Unknown'

View File

@ -0,0 +1,105 @@
import { getModelByName } from '../../../database/ObjectModels'
const normalizeType = (type) =>
typeof type === 'string'
? `${type.charAt(0).toLowerCase()}${type.slice(1)}`
: type
const getReferenceId = (value) => {
if (typeof value === 'string') return value
return value?._id?._id || value?._id || null
}
const addCandidate = (candidates, candidate) => {
if (!candidate?.email) return
const key = `${candidate.email.toLowerCase()}:${candidate.recipientType || ''}:${
getReferenceId(candidate.recipient) || ''
}`
if (!candidates.some((entry) => entry.key === key)) {
candidates.push({ ...candidate, key })
}
}
export const resolveEmailRecipients = async ({
type,
objectData,
fetchObject
}) => {
if (!type || !objectData) return []
const model = getModelByName(type, true)
if (model?.name === 'unknown') return []
const candidates = []
const properties = Array.isArray(model.properties) ? model.properties : []
for (const property of properties) {
const value = objectData[property.name]
if (property.type === 'email' && typeof value === 'string' && value.trim()) {
addCandidate(candidates, {
email: value.trim(),
label: `${property.label || property.name}: ${value.trim()}`,
property: property.name
})
continue
}
if (property.type !== 'object' || value == null) continue
const resolvedType = normalizeType(
typeof property.objectType === 'function'
? property.objectType(objectData)
: property.objectType
)
if (resolvedType !== 'client' && resolvedType !== 'vendor') continue
let recipient = value
const recipientId = getReferenceId(value)
if (
recipientId &&
(typeof recipient !== 'object' || typeof recipient.email !== 'string')
) {
recipient = await fetchObject(recipientId, resolvedType)
}
if (typeof recipient?.email === 'string' && recipient.email.trim()) {
addCandidate(candidates, {
email: recipient.email.trim(),
label: `${property.label || property.name}: ${
recipient.name || recipient._reference || recipient.email
}`,
property: property.name,
recipientType: resolvedType,
recipient
})
}
}
return candidates
}
/** Prefer a client/vendor ref; otherwise use a plain email candidate. */
export const getDefaultEmailRecipient = (candidates = []) => {
if (!Array.isArray(candidates) || !candidates.length) return null
const objectCandidate = candidates.find(
(candidate) =>
(candidate.recipientType === 'client' ||
candidate.recipientType === 'vendor') &&
candidate.recipient &&
candidate.email
)
if (objectCandidate) {
return {
recipientEmail: objectCandidate.email,
recipientType: objectCandidate.recipientType,
recipient: objectCandidate.recipient
}
}
const emailCandidate = candidates.find((candidate) => candidate.email)
if (!emailCandidate) return null
return { recipientEmail: emailCandidate.email }
}

View File

@ -0,0 +1,8 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/emailaccounticon.svg?react'
const EmailAccountIcon = (props) => (
<Icon component={CustomIconSvg} {...props} />
)
export default EmailAccountIcon

View File

@ -0,0 +1 @@
export { default } from './MailIcon.jsx'

View File

@ -0,0 +1,8 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/mailtemplateicon.svg?react'
const EmailTemplateIcon = (props) => (
<Icon component={CustomIconSvg} {...props} />
)
export default EmailTemplateIcon

View File

@ -58,6 +58,9 @@ import ReturnPolicyIcon from './ReturnPolicyIcon'
import PaymentPolicyIcon from './PaymentPolicyIcon' import PaymentPolicyIcon from './PaymentPolicyIcon'
import AppPasswordIcon from './AppPasswordIcon' import AppPasswordIcon from './AppPasswordIcon'
import InfoCircleIcon from './InfoCircleIcon' import InfoCircleIcon from './InfoCircleIcon'
import MailIcon from './MailIcon'
import EmailAccountIcon from './EmailAccountIcon'
import EmailTemplateIcon from './EmailTemplateIcon'
const toEmoji = (emoji) => <span aria-hidden>{emoji}</span> const toEmoji = (emoji) => <span aria-hidden>{emoji}</span>
@ -121,6 +124,10 @@ const sidebarIconMap = {
taxRate: <TaxRateIcon />, taxRate: <TaxRateIcon />,
taxRecord: <TaxRecordIcon />, taxRecord: <TaxRecordIcon />,
appPassword: <AppPasswordIcon />, appPassword: <AppPasswordIcon />,
email: <MailIcon />,
emailAccount: <EmailAccountIcon />,
emailTemplate: <EmailTemplateIcon />,
emailMessage: <MailIcon />,
infoCircle: <InfoCircleIcon />, infoCircle: <InfoCircleIcon />,
sessionStorage: toEmoji('🗃️'), sessionStorage: toEmoji('🗃️'),
authDebug: toEmoji('🔐'), authDebug: toEmoji('🔐'),

View File

@ -41,6 +41,9 @@ import { DocumentSize } from './models/DocumentSize.js'
import { DocumentTemplate } from './models/DocumentTemplate.js' import { DocumentTemplate } from './models/DocumentTemplate.js'
import { DocumentPrinter } from './models/DocumentPrinter.js' import { DocumentPrinter } from './models/DocumentPrinter.js'
import { DocumentJob } from './models/DocumentJob.js' import { DocumentJob } from './models/DocumentJob.js'
import { EmailAccount } from './models/EmailAccount.js'
import { EmailTemplate } from './models/EmailTemplate.js'
import { EmailMessage } from './models/EmailMessage.js'
import { TaxRate } from './models/TaxRate.js' import { TaxRate } from './models/TaxRate.js'
import { TaxRecord } from './models/TaxRecord.js' import { TaxRecord } from './models/TaxRecord.js'
import { Invoice } from './models/Invoice.js' import { Invoice } from './models/Invoice.js'
@ -99,6 +102,9 @@ export const objectModels = [
DocumentTemplate, DocumentTemplate,
DocumentPrinter, DocumentPrinter,
DocumentJob, DocumentJob,
EmailAccount,
EmailTemplate,
EmailMessage,
TaxRate, TaxRate,
TaxRecord, TaxRecord,
Invoice, Invoice,
@ -158,6 +164,9 @@ export {
DocumentTemplate, DocumentTemplate,
DocumentPrinter, DocumentPrinter,
DocumentJob, DocumentJob,
EmailAccount,
EmailTemplate,
EmailMessage,
TaxRate, TaxRate,
TaxRecord, TaxRecord,
Invoice, Invoice,

View File

@ -28,6 +28,7 @@ export const AuditLog = {
filters: [ filters: [
'parent', 'parent',
'owner', 'owner',
'system',
'operation', 'operation',
'createdAt', 'createdAt',
'updatedAt', 'updatedAt',
@ -74,16 +75,23 @@ export const AuditLog = {
{ {
name: 'owner', name: 'owner',
label: 'Owner', label: 'Owner',
type: 'object', type: (objectData) => (objectData.system ? 'text' : 'object'),
objectType: (objectData) => { objectType: (objectData) => {
return objectData.ownerType return objectData.ownerType
}, },
columnFixed: 'left', columnFixed: 'left',
value: null, value: (objectData) => (objectData.system ? 'System' : objectData.owner),
showCopy: true, showCopy: true,
showHyperlink: true, showHyperlink: true,
columnWidth: 200 columnWidth: 200
}, },
{
name: 'system',
label: 'System',
type: 'bool',
readOnly: true,
columnWidth: 100
},
{ {
name: 'parent', name: 'parent',
label: 'Parent', label: 'Parent',

View File

@ -0,0 +1,233 @@
import { createElement, lazy } from 'react'
const EmailAccountInfo = lazy(
() =>
import('../../components/Dashboard/Management/EmailAccounts/EmailAccountInfo')
)
const NewEmailAccount = lazy(
() =>
import('../../components/Dashboard/Management/EmailAccounts/NewEmailAccount')
)
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ListIcon from '../../components/Icons/ListIcon'
import EmailAccountIcon from '../../components/Icons/EmailAccountIcon'
export const EmailAccount = {
name: 'emailAccount',
label: 'Email Account',
labelPlural: 'Email Accounts',
endpoint: 'emailaccounts',
url: '/dashboard/management/emailaccounts',
prefix: 'EAC',
icon: EmailAccountIcon,
actions: [
{
name: 'new',
type: 'modal',
pageName: 'list',
modalWidth: 700,
label: 'New Email Account',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
return createElement(NewEmailAccount, {
defaultValues: objectData,
onOk,
reset: true
})
}
},
{
name: 'list',
type: 'page',
pageName: 'list',
label: 'List',
icon: ListIcon
},
{
name: 'info',
type: 'page',
pageName: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon
},
{
name: 'edit',
type: 'page',
pageName: 'info',
label: 'Edit',
row: true,
icon: EditIcon,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
type: 'page',
pageName: 'info',
icon: XMarkIcon,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'finishEdit',
label: 'Save Edits',
type: 'page',
pageName: 'info',
icon: CheckIcon,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
}
],
pages: [
{
name: 'info',
content: () => createElement(EmailAccountInfo)
}
],
columns: [
'_reference',
'name',
'active',
'host',
'port',
'secure',
'username',
'fromName',
'fromEmail',
'createdAt',
'updatedAt'
],
filters: [
'name',
'active',
'host',
'secure',
'username',
'fromEmail',
'createdAt',
'updatedAt',
'_reference'
],
group: ['active', 'secure'],
sorters: [
'name',
'active',
'host',
'port',
'secure',
'createdAt',
'updatedAt'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
objectType: 'emailAccount',
showCopy: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
objectType: 'emailAccount',
showCopy: true,
readOnly: true,
columnFixed: 'left',
columnWidth: 180
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
columnFixed: 'left',
columnWidth: 200
},
{
name: 'active',
label: 'Active',
type: 'bool',
required: true,
columnWidth: 125
},
{
name: 'host',
label: 'SMTP Host',
type: 'text',
required: true,
columnWidth: 220
},
{
name: 'port',
label: 'SMTP Port',
type: 'number',
required: true,
min: 1,
max: 65535,
step: 1,
columnWidth: 125
},
{
name: 'secure',
label: 'Secure',
type: 'bool',
required: true,
columnWidth: 125
},
{
name: 'username',
label: 'Username',
type: 'text',
required: false,
columnWidth: 220
},
{
name: 'password',
label: 'Password',
type: 'secret',
required: false,
value: (data) => (data?._id ? undefined : data?.password),
columnWidth: 200
},
{
name: 'fromName',
label: 'From Name',
type: 'text',
required: true,
columnWidth: 200
},
{
name: 'fromEmail',
label: 'From Email',
type: 'email',
required: true,
columnWidth: 260
}
]
}

View File

@ -0,0 +1,331 @@
import { createElement, lazy } from 'react'
import dayjs from 'dayjs'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import ListIcon from '../../components/Icons/ListIcon'
import EmailMessageIcon from '../../components/Icons/EmailMessageIcon'
const EmailMessageInfo = lazy(
() =>
import('../../components/Dashboard/Management/EmailMessages/EmailMessageInfo')
)
const NewEmailMessage = lazy(
() =>
import('../../components/Dashboard/Management/EmailMessages/NewEmailMessage')
)
const readOnlyAfterCreate = (data) => data?._id != null
export const EmailMessage = {
name: 'emailMessage',
label: 'Email Message',
labelPlural: 'Email Messages',
endpoint: 'emailmessages',
url: '/dashboard/management/emailmessages',
prefix: 'EMS',
icon: EmailMessageIcon,
readOnly: true,
actions: [
{
name: 'new',
type: 'modal',
pageName: 'list',
modalWidth: 1100,
label: 'New Email Message',
icon: PlusIcon,
content: (objectData, { onOk } = {}) =>
createElement(NewEmailMessage, { defaultValues: objectData, onOk })
},
{
name: 'list',
type: 'page',
pageName: 'list',
label: 'List',
icon: ListIcon
},
{
name: 'info',
type: 'page',
pageName: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon
}
],
pages: [
{
name: 'info',
content: () => createElement(EmailMessageInfo)
}
],
columns: [
'_reference',
'name',
'state',
'recipientEmail',
'fromEmail',
'messageId',
'emailTemplate',
'emailAccount',
'objectType',
'sentAt',
'readAt',
'createdAt',
'updatedAt'
],
filters: [
'name',
'state',
'recipientEmail',
'fromEmail',
'messageId',
'emailTemplate',
'emailAccount',
'objectType',
'recipientType',
'read',
'sentAt',
'readAt',
'createdAt',
'updatedAt',
'_reference'
],
group: [
'state',
'emailTemplate',
'emailAccount',
'objectType',
'recipientType'
],
sorters: [
'name',
'state',
'recipientEmail',
'fromEmail',
'messageId',
'sentAt',
'readAt',
'createdAt',
'updatedAt'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
objectType: 'emailMessage',
showCopy: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
objectType: 'emailMessage',
showCopy: true,
readOnly: true,
columnFixed: 'left',
columnWidth: 180
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
readOnly: readOnlyAfterCreate,
value: (data) =>
data?.createdAt == null
? `${data?.emailTemplate?.name || 'Email'} ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
: undefined,
columnFixed: 'left',
columnWidth: 240
},
{
name: 'sentAt',
label: 'Sent At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: 'state',
label: 'Status',
type: 'state',
showName: false,
readOnly: true,
columnWidth: 240
},
{
name: 'readAt',
label: 'Read At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: 'read',
label: 'Read',
type: 'bool',
readOnly: true,
columnWidth: 100
},
{
name: 'emailTemplate',
label: 'Template',
type: 'object',
objectType: 'emailTemplate',
required: true,
readOnly: readOnlyAfterCreate,
showHyperlink: true,
masterFilter: (data) => ({
active: true,
global: false,
objectType: data?.objectType
}),
columnWidth: 200
},
{
name: 'objectType',
label: 'Object Type',
type: 'objectType',
required: true,
readOnly: readOnlyAfterCreate,
columnWidth: 180
},
{
name: 'object',
label: 'Object',
type: 'object',
objectType: (data) => data?.objectType,
required: true,
readOnly: readOnlyAfterCreate,
showHyperlink: true,
columnWidth: 200
},
{
name: 'emailAccount',
label: 'Email Account',
type: 'object',
objectType: 'emailAccount',
required: true,
readOnly: readOnlyAfterCreate,
showHyperlink: true,
masterFilter: { active: true },
columnWidth: 200
},
{
name: 'recipientEmail',
label: 'Recipient',
type: 'email',
required: true,
readOnly: readOnlyAfterCreate,
options: (data) =>
Array.isArray(data?._recipientCandidates) &&
data._recipientCandidates.length
? data._recipientCandidates.map((candidate) => ({
value: candidate.email,
label: candidate.label
}))
: undefined,
columnWidth: 260
},
{
name: 'recipientType',
label: 'Recipient Type',
type: 'objectType',
readOnly: readOnlyAfterCreate,
value: (data) => {
if (
!Array.isArray(data?._recipientCandidates) ||
!data._recipientCandidates.length
) {
return undefined
}
const match = data._recipientCandidates.find(
(candidate) => candidate.email === data?.recipientEmail
)
if (!match) return undefined
return match.recipientType ?? null
},
columnWidth: 160
},
{
name: 'recipient',
label: 'Recipient Object',
type: 'object',
objectType: (data) => data?.recipientType,
readOnly: readOnlyAfterCreate,
showHyperlink: true,
value: (data) => {
if (
!Array.isArray(data?._recipientCandidates) ||
!data._recipientCandidates.length
) {
return undefined
}
const match = data._recipientCandidates.find(
(candidate) => candidate.email === data?.recipientEmail
)
if (!match) return undefined
return match.recipient ?? null
},
columnWidth: 200
},
{
name: 'fromEmail',
label: 'From Email',
type: 'email',
required: true,
readOnly: true,
value: (data) => data?.emailAccount?.fromEmail,
columnWidth: 260
},
{
name: 'messageId',
label: 'Message ID',
type: 'text',
readOnly: true,
showCopy: true,
columnWidth: 280
},
{
name: 'subject',
label: 'Rendered Subject',
type: 'text',
readOnly: true,
columnWidth: 300
},
{
name: 'content',
label: 'Rendered Body',
type: 'codeBlock',
language: 'html',
readOnly: true
},
{
name: 'attachments',
label: 'Attachments',
type: 'objectList',
objectType: 'file',
readOnly: true,
showHyperlink: true,
columnWidth: 230
}
]
}

View File

@ -0,0 +1,313 @@
import { createElement, lazy } from 'react'
const EmailTemplateInfo = lazy(
() =>
import('../../components/Dashboard/Management/EmailTemplates/EmailTemplateInfo')
)
const NewEmailTemplate = lazy(
() =>
import('../../components/Dashboard/Management/EmailTemplates/NewEmailTemplate')
)
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import DesignIcon from '../../components/Icons/DesignIcon'
import ListIcon from '../../components/Icons/ListIcon'
import EmailTemplateIcon from '../../components/Icons/EmailTemplateIcon'
export const EmailTemplate = {
name: 'emailTemplate',
label: 'Email Template',
labelPlural: 'Email Templates',
endpoint: 'emailtemplates',
url: '/dashboard/management/emailtemplates',
prefix: 'ETP',
icon: EmailTemplateIcon,
actions: [
{
name: 'new',
type: 'modal',
pageName: 'list',
modalWidth: 700,
label: 'New Email Template',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
return createElement(NewEmailTemplate, {
defaultValues: objectData,
onOk,
reset: true
})
}
},
{
name: 'list',
type: 'page',
pageName: 'list',
label: 'List',
icon: ListIcon
},
{
name: 'design',
label: 'Design',
row: true,
icon: DesignIcon,
url: (_id) =>
`/dashboard/management/emailtemplates/design?emailTemplateId=${_id}`
},
{
name: 'info',
type: 'page',
pageName: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon
},
{
name: 'edit',
type: 'page',
pageName: 'info',
label: 'Edit',
row: true,
icon: EditIcon,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
type: 'page',
pageName: 'info',
icon: XMarkIcon,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'finishEdit',
label: 'Save Edits',
type: 'page',
pageName: 'info',
icon: CheckIcon,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
}
],
pages: [
{
name: 'info',
content: () => createElement(EmailTemplateInfo)
}
],
columns: [
'_reference',
'name',
'active',
'global',
'objectType',
'tags',
'parent',
'referencedTemplates',
'createdAt',
'updatedAt'
],
filters: [
'name',
'tags',
'active',
'global',
'parent',
'referencedTemplates',
'objectType',
'createdAt',
'updatedAt',
'_reference'
],
group: ['tags', 'objectType'],
sorters: [
'name',
'parent',
'createdAt',
'updatedAt',
'active',
'global',
'objectType'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
objectType: 'emailTemplate',
showCopy: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
objectType: 'emailTemplate',
showCopy: true,
readOnly: true,
columnFixed: 'left',
columnWidth: 180
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 200
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
columnFixed: 'left',
columnWidth: 200
},
{
name: 'objectType',
label: 'Object Type',
type: 'objectType',
required: true,
empty: (data) => data?.global,
columnWidth: 180
},
{
name: 'active',
label: 'Active',
type: 'bool',
required: true,
columnWidth: 125
},
{ name: 'tags', label: 'Tags', type: 'tags', columnWidth: 200 },
{
name: 'global',
label: 'Global',
type: 'bool',
required: true,
columnWidth: 125
},
{
name: 'parent',
label: 'Parent',
type: 'object',
objectType: 'emailTemplate',
masterFilter: { global: true, active: true },
showHyperlink: true,
empty: (data) => data?.global,
columnWidth: 230
},
{
name: 'referencedTemplates',
label: 'Referenced Templates',
type: 'objectList',
objectType: 'emailTemplate',
readOnly: true,
showHyperlink: true,
columnWidth: 230
},
{
name: 'subject',
label: 'Subject',
type: 'text',
required: true,
columnWidth: 300
},
{
name: 'content',
label: 'Content',
type: 'codeBlock',
language: 'fcTemplateLang'
},
{
name: 'testObject',
label: 'Test Object',
type: 'object',
objectType: (data) => data?.objectType,
showHyperlink: true
},
{
name: 'attachments',
label: 'Attachments',
type: 'objectChildren',
required: false,
canAddRemove: true,
size: 'medium',
columns: ['type', 'file', 'fileName', 'fileType'],
properties: [
{
name: 'type',
label: 'Type',
type: 'objectType',
required: true,
columnWidth: 180,
masterFilter: ['file', 'documentTemplate']
},
{
name: 'file',
label: 'File',
type: 'object',
objectType: (row) => row?.type,
required: true,
showHyperlink: true,
columnWidth: 230,
masterFilter: (row, parentData) => {
if (row?.type === 'documentTemplate') {
return {
active: true,
global: false,
objectType: parentData?.objectType
}
}
return undefined
}
},
{
name: 'fileName',
label: 'File Name',
type: 'text',
required: true,
columnWidth: 200,
value: (row) => {
if (row?.fileName) return row.fileName
return row?.file?.name
}
},
{
name: 'fileType',
label: 'File Type',
type: 'select',
columnWidth: 140,
disabled: (row) => row?.type !== 'documentTemplate',
options: [
{ value: 'pdf', label: 'PDF' },
{ value: 'jpeg', label: 'JPEG' },
{ value: 'png', label: 'PNG' },
{ value: 'svg', label: 'SVG' }
],
value: (row) => {
if (row?.type === 'documentTemplate') {
return row?.fileType || 'pdf'
}
return row?.fileType
}
}
]
}
]
}

View File

@ -117,6 +117,31 @@ const managementSidebarItems = [
} }
] ]
}, },
{
key: 'email',
iconKey: 'email',
label: 'Email',
children: [
{
key: 'emailAccounts',
iconKey: 'emailAccount',
label: 'Email Accounts',
path: '/dashboard/management/emailaccounts'
},
{
key: 'emailMessages',
iconKey: 'emailMessage',
label: 'Email Messages',
path: '/dashboard/management/emailmessages'
},
{
key: 'emailTemplates',
iconKey: 'emailTemplate',
label: 'Email Templates',
path: '/dashboard/management/emailtemplates'
}
]
},
{ type: 'divider' }, { type: 'divider' },
{ {
key: 'hosts', key: 'hosts',

View File

@ -69,6 +69,19 @@ const DocumentTemplateDesign = lazy(
() => () =>
import('../components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx') import('../components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx')
) )
const EmailAccounts = lazy(
() => import('../components/Dashboard/Management/EmailAccounts.jsx')
)
const EmailTemplates = lazy(
() => import('../components/Dashboard/Management/EmailTemplates.jsx')
)
const EmailMessages = lazy(
() => import('../components/Dashboard/Management/EmailMessages.jsx')
)
const EmailTemplateDesign = lazy(
() =>
import('../components/Dashboard/Management/EmailTemplates/EmailTemplateDesign.jsx')
)
const Files = lazy(() => import('../components/Dashboard/Management/Files.jsx')) const Files = lazy(() => import('../components/Dashboard/Management/Files.jsx'))
const TaxRates = lazy( const TaxRates = lazy(
() => import('../components/Dashboard/Management/TaxRates.jsx') () => import('../components/Dashboard/Management/TaxRates.jsx')
@ -146,6 +159,26 @@ const ManagementRoutes = [
path='management/documenttemplates/design' path='management/documenttemplates/design'
element={<DocumentTemplateDesign />} element={<DocumentTemplateDesign />}
/>, />,
<Route
key='emailaccounts'
path='management/emailaccounts'
element={<EmailAccounts />}
/>,
<Route
key='emailtemplates'
path='management/emailtemplates'
element={<EmailTemplates />}
/>,
<Route
key='emailmessages'
path='management/emailmessages'
element={<EmailMessages />}
/>,
<Route
key='emailtemplates-design'
path='management/emailtemplates/design'
element={<EmailTemplateDesign />}
/>,
<Route <Route
key='apppasswords' key='apppasswords'
path='management/apppasswords' path='management/apppasswords'