Implemented notifications.
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
This commit is contained in:
parent
1e0f125f21
commit
2622fae555
@ -34,6 +34,7 @@
|
|||||||
.g2-tooltip-list-item,
|
.g2-tooltip-list-item,
|
||||||
.ant-picker-input,
|
.ant-picker-input,
|
||||||
.ant-picker-header-view button,
|
.ant-picker-header-view button,
|
||||||
|
.ant-badge,
|
||||||
[class*=' ant-radio'] {
|
[class*=' ant-radio'] {
|
||||||
font-family: 'DM Sans';
|
font-family: 'DM Sans';
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/App.jsx
17
src/App.jsx
@ -24,6 +24,7 @@ import {
|
|||||||
} from './components/Dashboard/context/ThemeContext'
|
} from './components/Dashboard/context/ThemeContext'
|
||||||
import AppError from './components/App/AppError'
|
import AppError from './components/App/AppError'
|
||||||
import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.jsx'
|
import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.jsx'
|
||||||
|
import { NotificationProvider } from './components/Dashboard/context/NotificationContext.jsx'
|
||||||
import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx'
|
import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx'
|
||||||
import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx'
|
import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx'
|
||||||
import AuthCallback from './components/App/AuthCallback.jsx'
|
import AuthCallback from './components/App/AuthCallback.jsx'
|
||||||
@ -59,9 +60,10 @@ const AppContent = () => {
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<PrintServerProvider>
|
<PrintServerProvider>
|
||||||
<ApiServerProvider>
|
<ApiServerProvider>
|
||||||
<SpotlightProvider>
|
<MessageProvider>
|
||||||
<ActionsModalProvider>
|
<NotificationProvider>
|
||||||
<MessageProvider>
|
<SpotlightProvider>
|
||||||
|
<ActionsModalProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path='/dashboard/electron/spotlightcontent'
|
path='/dashboard/electron/spotlightcontent'
|
||||||
@ -112,10 +114,11 @@ const AppContent = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</MessageProvider>
|
</ActionsModalProvider>
|
||||||
</ActionsModalProvider>
|
</SpotlightProvider>
|
||||||
</SpotlightProvider>
|
</NotificationProvider>
|
||||||
|
</MessageProvider>
|
||||||
</ApiServerProvider>
|
</ApiServerProvider>
|
||||||
</PrintServerProvider>
|
</PrintServerProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@ -22,6 +22,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import {
|
import {
|
||||||
getModelByName,
|
getModelByName,
|
||||||
@ -134,6 +135,11 @@ const InvoiceInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='invoice'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='invoice'
|
type='invoice'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.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'
|
||||||
import PostPayment from './PostPayment.jsx'
|
import PostPayment from './PostPayment.jsx'
|
||||||
@ -109,6 +110,11 @@ const PaymentInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='payment'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='payment'
|
type='payment'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import NoteIcon from '../../../Icons/NoteIcon'
|
|||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon'
|
||||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
|
||||||
import DocumentPrintButton from '../../common/DocumentPrintButton'
|
import DocumentPrintButton from '../../common/DocumentPrintButton'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle'
|
||||||
import ScrollBox from '../../common/ScrollBox'
|
import ScrollBox from '../../common/ScrollBox'
|
||||||
|
|
||||||
const FilamentStockInfo = () => {
|
const FilamentStockInfo = () => {
|
||||||
@ -91,6 +92,11 @@ const FilamentStockInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='filamentStock'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='filamentStock'
|
type='filamentStock'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import ActionHandler from '../../common/ActionHandler.jsx'
|
|||||||
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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('OrderItemInfo')
|
const log = loglevel.getLogger('OrderItemInfo')
|
||||||
@ -99,6 +100,11 @@ const OrderItemInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='orderItem'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
<LockIndicator lock={objectFormState.lock} />
|
<LockIndicator lock={objectFormState.lock} />
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('PartStockInfo')
|
const log = loglevel.getLogger('PartStockInfo')
|
||||||
@ -97,6 +98,11 @@ const PartStockInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='partStock'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='partStock'
|
type='partStock'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.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'
|
||||||
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
|
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
|
||||||
@ -159,6 +160,11 @@ const PurchaseOrderInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='purchaseOrder'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='purchaseOrder'
|
type='purchaseOrder'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||||
import ShipShipment from './ShipShipment.jsx'
|
import ShipShipment from './ShipShipment.jsx'
|
||||||
@ -119,6 +120,11 @@ const ShipmentInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='shipment'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='shipment'
|
type='shipment'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('StockAuditInfo')
|
const log = loglevel.getLogger('StockAuditInfo')
|
||||||
@ -97,6 +98,11 @@ const StockAuditInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='stockAudit'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='stockAudit'
|
type='stockAudit'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('CourierServiceInfo')
|
const log = loglevel.getLogger('CourierServiceInfo')
|
||||||
@ -97,6 +98,11 @@ const CourierServiceInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='courierService'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='courierService'
|
type='courierService'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import CourierServiceIcon from '../../../Icons/CourierServiceIcon.jsx'
|
import CourierServiceIcon from '../../../Icons/CourierServiceIcon.jsx'
|
||||||
|
|
||||||
@ -93,6 +94,11 @@ const CourierInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='courier'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='courier'
|
type='courier'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('DocumentJobInfo')
|
const log = loglevel.getLogger('DocumentJobInfo')
|
||||||
@ -94,6 +95,11 @@ const DocumentJobInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='documentJob'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='documentJob'
|
type='documentJob'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('DocumentPrinterInfo')
|
const log = loglevel.getLogger('DocumentPrinterInfo')
|
||||||
@ -102,6 +103,11 @@ const DocumentPrinterInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='documentPrinter'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='documentPrinter'
|
type='documentPrinter'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('DocumentSizeInfo')
|
const log = loglevel.getLogger('DocumentSizeInfo')
|
||||||
@ -94,6 +95,11 @@ const DocumentSizeInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='documentSize'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='documentSize'
|
type='documentSize'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('DocumentTemplateInfo')
|
const log = loglevel.getLogger('DocumentTemplateInfo')
|
||||||
@ -99,6 +100,11 @@ const DocumentTemplateInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='documentTemplate'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='documentTemplate'
|
type='documentTemplate'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
|||||||
import FilamentIcon from '../../../Icons/FilamentIcon.jsx'
|
import FilamentIcon from '../../../Icons/FilamentIcon.jsx'
|
||||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('FilamentInfo')
|
const log = loglevel.getLogger('FilamentInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -99,6 +100,11 @@ const FilamentInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='filament'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='filament'
|
type='filament'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.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'
|
||||||
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
|
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
|
||||||
@ -104,6 +105,11 @@ const FileInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='file'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='file'
|
type='file'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import ObjectTable from '../../common/ObjectTable.jsx'
|
|||||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
import HostOTP from './HostOtp.jsx'
|
import HostOTP from './HostOtp.jsx'
|
||||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.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'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
@ -105,6 +106,11 @@ const HostInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='host'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='host'
|
type='host'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -16,6 +16,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const NoteTypeInfo = () => {
|
const NoteTypeInfo = () => {
|
||||||
@ -83,6 +84,11 @@ const NoteTypeInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='noteType'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='noteType'
|
type='noteType'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('NoteInfo')
|
const log = loglevel.getLogger('NoteInfo')
|
||||||
@ -92,6 +93,11 @@ const NoteInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='note'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='note'
|
type='note'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -17,6 +17,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const PartInfo = () => {
|
const PartInfo = () => {
|
||||||
@ -35,7 +36,8 @@ const PartInfo = () => {
|
|||||||
editLoading: false,
|
editLoading: false,
|
||||||
formValid: false,
|
formValid: false,
|
||||||
lock: null,
|
lock: null,
|
||||||
loading: false
|
loading: false,
|
||||||
|
objectData: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -83,6 +85,11 @@ const PartInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='part'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='part'
|
type='part'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import ActionHandler from '../../common/ActionHandler.jsx'
|
|||||||
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 DocumentPrintButton from '../../common/DocumentPrintButton.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'
|
||||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
@ -38,7 +39,8 @@ const ProductInfo = () => {
|
|||||||
editLoading: false,
|
editLoading: false,
|
||||||
formValid: false,
|
formValid: false,
|
||||||
lock: null,
|
lock: null,
|
||||||
loading: false
|
loading: false,
|
||||||
|
objectData: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -87,6 +89,11 @@ const ProductInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='product'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='product'
|
type='product'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('TaxRateInfo')
|
const log = loglevel.getLogger('TaxRateInfo')
|
||||||
@ -92,6 +93,11 @@ const TaxRateInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='taxRate'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='taxRate'
|
type='taxRate'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('TaxRecordInfo')
|
const log = loglevel.getLogger('TaxRecordInfo')
|
||||||
@ -95,6 +96,11 @@ const TaxRecordInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='taxRecord'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='taxRecord'
|
type='taxRecord'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -18,6 +18,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const UserInfo = () => {
|
const UserInfo = () => {
|
||||||
@ -84,6 +85,11 @@ const UserInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='user'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='user'
|
type='user'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('VendorInfo')
|
const log = loglevel.getLogger('VendorInfo')
|
||||||
@ -92,6 +93,11 @@ const VendorInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='vendor'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='vendor'
|
type='vendor'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import ObjectTable from '../../common/ObjectTable.jsx'
|
|||||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
import EyeIcon from '../../../Icons/EyeIcon.jsx'
|
import EyeIcon from '../../../Icons/EyeIcon.jsx'
|
||||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
|
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
|
||||||
import FilePreview from '../../common/FilePreview.jsx'
|
import FilePreview from '../../common/FilePreview.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
@ -106,6 +107,11 @@ const GCodeFileInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='gcodeFile'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='gcodeFile'
|
type='gcodeFile'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ 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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import DeployJob from './DeployJob.jsx'
|
import DeployJob from './DeployJob.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
@ -105,6 +106,11 @@ const JobInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='job'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='job'
|
type='job'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('PrinterInfo')
|
const log = loglevel.getLogger('PrinterInfo')
|
||||||
@ -94,6 +95,11 @@ const PrinterInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='printer'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='printer'
|
type='printer'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -18,6 +18,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import StockEventIcon from '../../../Icons/StockEventIcon.jsx'
|
import StockEventIcon from '../../../Icons/StockEventIcon.jsx'
|
||||||
|
|
||||||
@ -85,6 +86,11 @@ const SubJobInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='subJob'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='subJob'
|
type='subJob'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -19,6 +19,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('ClientInfo')
|
const log = loglevel.getLogger('ClientInfo')
|
||||||
@ -92,6 +93,11 @@ const ClientInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='client'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='client'
|
type='client'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,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 DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.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'
|
||||||
import NewOrderItem from '../../Inventory/OrderItems/NewOrderItem.jsx'
|
import NewOrderItem from '../../Inventory/OrderItems/NewOrderItem.jsx'
|
||||||
@ -156,6 +157,11 @@ const SalesOrderInfo = () => {
|
|||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='salesOrder'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='salesOrder'
|
type='salesOrder'
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import { SpotlightContext } from '../context/SpotlightContext'
|
import { SpotlightContext } from '../context/SpotlightContext'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
|
import { NotificationContext } from '../context/NotificationContext'
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
import { Header } from 'antd/es/layout/layout'
|
import { Header } from 'antd/es/layout/layout'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
@ -46,8 +47,9 @@ const { Text } = Typography
|
|||||||
const DashboardNavigation = () => {
|
const DashboardNavigation = () => {
|
||||||
const { logout, userProfile } = useContext(AuthContext)
|
const { logout, userProfile } = useContext(AuthContext)
|
||||||
const { showSpotlight } = useContext(SpotlightContext)
|
const { showSpotlight } = useContext(SpotlightContext)
|
||||||
const { toggleNotificationCenter, unreadCount } = useContext(ApiServerContext)
|
|
||||||
const { connecting, connected } = useContext(ApiServerContext)
|
const { connecting, connected } = useContext(ApiServerContext)
|
||||||
|
const { toggleNotificationCenter, unreadCount } =
|
||||||
|
useContext(NotificationContext)
|
||||||
const [apiServerState, setApiServerState] = useState('disconnected')
|
const [apiServerState, setApiServerState] = useState('disconnected')
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -257,7 +259,7 @@ const DashboardNavigation = () => {
|
|||||||
onClick={() => showSpotlight()}
|
onClick={() => showSpotlight()}
|
||||||
/>
|
/>
|
||||||
</KeyboardShortcut>
|
</KeyboardShortcut>
|
||||||
<Badge count={unreadCount} size='small'>
|
<Badge count={unreadCount} size='small' offset={[-4, 5]}>
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
shortcut='alt+n'
|
shortcut='alt+n'
|
||||||
hint='ALT N'
|
hint='ALT N'
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Typography, Space, Tag, Badge, Flex, Card } from 'antd'
|
import { Typography, Button, Tag, Badge, Flex, Card, Divider } from 'antd'
|
||||||
import {
|
import {
|
||||||
BellOutlined,
|
BellOutlined,
|
||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
@ -7,37 +8,131 @@ import {
|
|||||||
CheckCircleOutlined
|
CheckCircleOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import TimeDisplay from './TimeDisplay'
|
import TimeDisplay from './TimeDisplay'
|
||||||
|
import EditIcon from '../../Icons/EditIcon'
|
||||||
|
import PropertyChanges from './PropertyChanges'
|
||||||
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
|
import ObjectDisplay from './ObjectDisplay'
|
||||||
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography
|
const { Text, Paragraph } = Typography
|
||||||
|
|
||||||
const Notification = ({ notification, onMarkAsRead }) => {
|
const Notification = ({
|
||||||
|
notification,
|
||||||
|
onMarkAsRead,
|
||||||
|
onDelete,
|
||||||
|
showCard = true,
|
||||||
|
showDelete = true,
|
||||||
|
showExtraInfo = true,
|
||||||
|
inlineIcon = false
|
||||||
|
}) => {
|
||||||
|
const [deleting, setDeleting] = useState(false)
|
||||||
|
|
||||||
const getNotificationIcon = (type) => {
|
const getNotificationIcon = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'info':
|
case 'info':
|
||||||
return <InfoCircleOutlined style={{ color: '#1890ff' }} />
|
return <InfoCircleOutlined />
|
||||||
case 'warning':
|
case 'editObject':
|
||||||
return <ExclamationCircleOutlined style={{ color: '#faad14' }} />
|
return <EditIcon />
|
||||||
|
case 'deleteObject':
|
||||||
|
return <BinIcon />
|
||||||
case 'error':
|
case 'error':
|
||||||
return <ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />
|
return <ExclamationCircleOutlined />
|
||||||
case 'success':
|
case 'success':
|
||||||
return <CheckCircleOutlined style={{ color: '#52c41a' }} />
|
return <CheckCircleOutlined />
|
||||||
default:
|
default:
|
||||||
return <BellOutlined style={{ color: '#8c8c8c' }} />
|
return <BellOutlined />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNotificationColor = (type) => {
|
const getNotificationTag = (type) => {
|
||||||
|
const icon = getNotificationIcon(type)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'info':
|
case 'info':
|
||||||
return 'blue'
|
return (
|
||||||
case 'warning':
|
<Tag color='blue' icon={icon} style={{ margin: 0 }}>
|
||||||
return 'orange'
|
Info
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
case 'editObject':
|
||||||
|
return (
|
||||||
|
<Tag color='blue' icon={icon} style={{ margin: 0 }}>
|
||||||
|
Edit
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
case 'deleteObject':
|
||||||
|
return (
|
||||||
|
<Tag color='error' icon={icon} style={{ margin: 0 }}>
|
||||||
|
Delete
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
case 'error':
|
case 'error':
|
||||||
return 'red'
|
return (
|
||||||
|
<Tag color='red' icon={icon} style={{ margin: 0 }}>
|
||||||
|
Error
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
case 'success':
|
case 'success':
|
||||||
return 'green'
|
return (
|
||||||
|
<Tag color='green' icon={icon} style={{ margin: 0 }}>
|
||||||
|
Success
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return 'default'
|
return (
|
||||||
|
<Tag color='default' icon={icon} style={{ margin: 0 }}>
|
||||||
|
Default
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMetadataDisplay = (metadata, type) => {
|
||||||
|
if (metadata.old && metadata.new && type === 'editObject') {
|
||||||
|
return (
|
||||||
|
<PropertyChanges
|
||||||
|
type={metadata?.objectType ?? 'unknown'}
|
||||||
|
value={{
|
||||||
|
old: metadata.old,
|
||||||
|
new: metadata.new
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNotificationMessage = (metadata, type) => {
|
||||||
|
const paragraph = {
|
||||||
|
ellipsis: {
|
||||||
|
rows: 2,
|
||||||
|
expandable: true,
|
||||||
|
symbol: 'Show more'
|
||||||
|
},
|
||||||
|
style: { margin: 0 }
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 'editObject': {
|
||||||
|
return (
|
||||||
|
<Paragraph {...paragraph}>
|
||||||
|
Object:
|
||||||
|
<ObjectDisplay
|
||||||
|
object={metadata.object}
|
||||||
|
objectType={metadata.objectType}
|
||||||
|
showHyperlink={true}
|
||||||
|
showSpotlight={true}
|
||||||
|
/>
|
||||||
|
User:
|
||||||
|
<ObjectDisplay
|
||||||
|
object={metadata.user}
|
||||||
|
objectType='user'
|
||||||
|
showHyperlink={true}
|
||||||
|
showSpotlight={true}
|
||||||
|
/>
|
||||||
|
</Paragraph>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return <Paragraph {...paragraph}>{notification.message}</Paragraph>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,52 +142,81 @@ const Notification = ({ notification, onMarkAsRead }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const handleDelete = async (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (onDelete) {
|
||||||
|
setDeleting(true)
|
||||||
|
try {
|
||||||
|
await onDelete(notification._id)
|
||||||
|
} finally {
|
||||||
|
setDeleting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Flex align='start' gap='small'>
|
||||||
|
<Flex vertical style={{ flex: 1 }} gap='small'>
|
||||||
|
<Flex justify='space-between' align='center'>
|
||||||
|
<Flex align='center' gap='middle'>
|
||||||
|
{inlineIcon && getNotificationIcon(notification.type)}
|
||||||
|
<Badge dot={!notification.read} offset={[2, 4]}>
|
||||||
|
<Text strong={!notification.read}>{notification.title}</Text>
|
||||||
|
</Badge>
|
||||||
|
</Flex>
|
||||||
|
{showDelete && (
|
||||||
|
<Button
|
||||||
|
type='text'
|
||||||
|
size='small'
|
||||||
|
loading={deleting}
|
||||||
|
disabled={deleting}
|
||||||
|
style={{
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
position: 'relative',
|
||||||
|
left: 2
|
||||||
|
}}
|
||||||
|
icon={
|
||||||
|
<XMarkIcon
|
||||||
|
style={{ fontSize: 10, marginBottom: 5, marginLeft: 0 }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={handleDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{getNotificationMessage(notification.metadata, notification.type)}
|
||||||
|
|
||||||
|
{notification.metadata &&
|
||||||
|
getMetadataDisplay(notification.metadata, notification.type)}
|
||||||
|
{showExtraInfo && (
|
||||||
|
<>
|
||||||
|
<Divider style={{ margin: 0 }} />
|
||||||
|
<Flex justify='space-between' align='center'>
|
||||||
|
{getNotificationTag(notification.type)}
|
||||||
|
<TimeDisplay dateTime={notification.createdAt} showSince={true} />
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
return showCard ? (
|
||||||
<Card
|
<Card
|
||||||
size='small'
|
size='small'
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: notification.read ? '#fafafa' : '#ffffff',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'background-color 0.2s',
|
transition: 'background-color 0.2s'
|
||||||
border: notification.read ? '1px solid #f0f0f0' : '1px solid #d9d9d9'
|
|
||||||
}}
|
}}
|
||||||
onClick={handleMarkAsRead}
|
onClick={handleMarkAsRead}
|
||||||
bodyStyle={{ padding: '12px' }}
|
styles={{ body: { padding: '10px 12px 12px 12px' } }}
|
||||||
>
|
>
|
||||||
<Flex align='start' gap='small'>
|
{content}
|
||||||
<Badge dot={!notification.read} offset={[-5, 5]}>
|
|
||||||
{getNotificationIcon(notification.type)}
|
|
||||||
</Badge>
|
|
||||||
<Flex vertical style={{ flex: 1 }} gap='small'>
|
|
||||||
<Flex justify='space-between' align='center'>
|
|
||||||
<Space>
|
|
||||||
<Text strong={!notification.read}>{notification.title}</Text>
|
|
||||||
<Tag color={getNotificationColor(notification.type)} size='small'>
|
|
||||||
{notification.type}
|
|
||||||
</Tag>
|
|
||||||
</Space>
|
|
||||||
<Text type='secondary' style={{ fontSize: '12px' }}>
|
|
||||||
<TimeDisplay dateTime={notification.createdAt} showSince={true} />
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
<Paragraph
|
|
||||||
ellipsis={{
|
|
||||||
rows: 2,
|
|
||||||
expandable: true,
|
|
||||||
symbol: 'Show more'
|
|
||||||
}}
|
|
||||||
style={{ margin: 0 }}
|
|
||||||
>
|
|
||||||
{notification.message}
|
|
||||||
</Paragraph>
|
|
||||||
{notification.metadata && (
|
|
||||||
<Text type='secondary' style={{ fontSize: '12px' }}>
|
|
||||||
{JSON.stringify(notification.metadata)}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginTop: '-1px' }}>{content}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +231,12 @@ Notification.propTypes = {
|
|||||||
createdAt: PropTypes.string.isRequired,
|
createdAt: PropTypes.string.isRequired,
|
||||||
metadata: PropTypes.object
|
metadata: PropTypes.object
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
onMarkAsRead: PropTypes.func
|
onMarkAsRead: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
showCard: PropTypes.bool,
|
||||||
|
showDelete: PropTypes.bool,
|
||||||
|
showExtraInfo: PropTypes.bool,
|
||||||
|
inlineIcon: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Notification
|
export default Notification
|
||||||
|
|||||||
@ -1,174 +1,62 @@
|
|||||||
import { useState, useEffect, useContext, useCallback } from 'react'
|
import { useContext, useState } from 'react'
|
||||||
import {
|
import { Typography, Button, Empty, Spin, Flex, Dropdown } from 'antd'
|
||||||
Typography,
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
Space,
|
|
||||||
Button,
|
|
||||||
Empty,
|
|
||||||
Spin,
|
|
||||||
Popconfirm,
|
|
||||||
Flex,
|
|
||||||
Badge,
|
|
||||||
Dropdown
|
|
||||||
} from 'antd'
|
|
||||||
import { useMessageContext } from '../context/MessageContext'
|
|
||||||
import {
|
|
||||||
BellOutlined,
|
|
||||||
DeleteOutlined,
|
|
||||||
CheckOutlined,
|
|
||||||
ReloadOutlined
|
|
||||||
} from '@ant-design/icons'
|
|
||||||
import axios from 'axios'
|
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { NotificationContext } from '../context/NotificationContext'
|
||||||
import config from '../../../config'
|
|
||||||
import Notification from './Notification'
|
import Notification from './Notification'
|
||||||
|
import ScrollBox from './ScrollBox'
|
||||||
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const NotificationCenter = ({ visible }) => {
|
const NotificationCenter = ({ visible }) => {
|
||||||
const [notifications, setNotifications] = useState([])
|
const {
|
||||||
const [loading, setLoading] = useState(false)
|
notifications,
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
markNotificationAsRead,
|
||||||
const { showSuccess, showError } = useMessageContext()
|
markAllNotificationsAsRead,
|
||||||
|
deleteNotification,
|
||||||
|
deleteAllNotifications,
|
||||||
|
notificationsLoading
|
||||||
|
} = useContext(NotificationContext)
|
||||||
|
|
||||||
const { authenticated } = useContext(AuthContext)
|
const unreadCount = notifications.filter((n) => !n.read).length
|
||||||
|
|
||||||
const fetchNotifications = useCallback(async () => {
|
const [clearNotificationsLoading, setClearNotificationsLoading] =
|
||||||
if (!authenticated) return
|
useState(false)
|
||||||
|
|
||||||
setLoading(true)
|
const handleMarkAsRead = async (notificationId) => {
|
||||||
try {
|
await markNotificationAsRead(notificationId)
|
||||||
const response = await axios.get(`${config.backendUrl}/notifications`, {
|
}
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
setNotifications(response.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching notifications:', error)
|
|
||||||
showError('Failed to fetch notifications')
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}, [authenticated, showError])
|
|
||||||
|
|
||||||
const markAsRead = useCallback(
|
const handleMarkAllAsRead = async () => {
|
||||||
async (notificationId) => {
|
await markAllNotificationsAsRead()
|
||||||
try {
|
}
|
||||||
await axios.put(
|
|
||||||
`${config.backendUrl}/notifications/${notificationId}/read`,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update local state
|
|
||||||
setNotifications((prev) =>
|
|
||||||
prev.map((notification) => {
|
|
||||||
if (notification._id === notificationId) {
|
|
||||||
return { ...notification, read: true }
|
|
||||||
}
|
|
||||||
return notification
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
showSuccess('Notification marked as read')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error marking notification as read:', error)
|
|
||||||
showError('Failed to mark notification as read')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[showSuccess, showError]
|
|
||||||
)
|
|
||||||
|
|
||||||
const markAllAsRead = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
await axios.put(
|
|
||||||
`${config.backendUrl}/notifications/read-all`,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update local state
|
|
||||||
setNotifications((prev) =>
|
|
||||||
prev.map((notification) => ({ ...notification, read: true }))
|
|
||||||
)
|
|
||||||
|
|
||||||
showSuccess('All notifications marked as read')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error marking all notifications as read:', error)
|
|
||||||
showError('Failed to mark all notifications as read')
|
|
||||||
}
|
|
||||||
}, [showSuccess, showError])
|
|
||||||
|
|
||||||
const deleteAllNotifications = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
await axios.delete(`${config.backendUrl}/notifications`, {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
|
|
||||||
setNotifications([])
|
|
||||||
showSuccess('All notifications deleted')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting all notifications:', error)
|
|
||||||
showError('Failed to delete all notifications')
|
|
||||||
} finally {
|
|
||||||
setShowDeleteConfirm(false)
|
|
||||||
}
|
|
||||||
}, [showSuccess, showError])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible && authenticated) {
|
|
||||||
fetchNotifications()
|
|
||||||
}
|
|
||||||
}, [visible, authenticated, fetchNotifications])
|
|
||||||
|
|
||||||
const unreadCount = notifications.filter(
|
|
||||||
(notification) => !notification.read
|
|
||||||
).length
|
|
||||||
|
|
||||||
const actionItems = {
|
const actionItems = {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Mark All Read',
|
label: 'Mark All Read',
|
||||||
key: 'markAllRead',
|
key: 'markAllRead',
|
||||||
icon: <CheckOutlined />,
|
icon: <CheckIcon />,
|
||||||
disabled: unreadCount === 0
|
disabled: unreadCount === 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Reload Notifications',
|
|
||||||
key: 'reloadNotifications',
|
|
||||||
icon: <ReloadOutlined />
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
label: 'Delete All',
|
label: 'Clear All',
|
||||||
key: 'deleteAll',
|
key: 'clearAll',
|
||||||
icon: <DeleteOutlined />,
|
icon: <BinIcon />,
|
||||||
danger: true,
|
danger: true,
|
||||||
|
|
||||||
disabled: notifications.length === 0
|
disabled: notifications.length === 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
onClick: ({ key }) => {
|
onClick: ({ key }) => {
|
||||||
if (key === 'markAllRead') {
|
if (key === 'markAllRead') {
|
||||||
markAllAsRead()
|
handleMarkAllAsRead()
|
||||||
} else if (key === 'reloadNotifications') {
|
} else if (key === 'clearAll') {
|
||||||
fetchNotifications()
|
setClearNotificationsLoading(true)
|
||||||
} else if (key === 'deleteAll') {
|
deleteAllNotifications().finally(() => setClearNotificationsLoading(false))
|
||||||
setShowDeleteConfirm(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,58 +66,45 @@ const NotificationCenter = ({ visible }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex vertical style={{ height: '100%' }}>
|
||||||
<Flex justify='space-between' align='center'>
|
<Flex justify='space-between' align='center' style={{ marginBottom: 16 }}>
|
||||||
<Space size='middle'>
|
<Dropdown menu={actionItems}>
|
||||||
<Dropdown menu={actionItems}>
|
<Button loading={clearNotificationsLoading}>Actions</Button>
|
||||||
<Button>Actions</Button>
|
</Dropdown>
|
||||||
</Dropdown>
|
|
||||||
<Badge count={unreadCount} size='small'>
|
|
||||||
<BellOutlined style={{ fontSize: '18px' }} />
|
|
||||||
</Badge>
|
|
||||||
</Space>
|
|
||||||
<Space>
|
|
||||||
<Button icon={<DeleteOutlined />} danger />
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<div style={{ maxHeight: 500, overflow: 'auto' }}>
|
<ScrollBox>
|
||||||
{loading ? (
|
{notificationsLoading ? (
|
||||||
<div style={{ padding: '40px', textAlign: 'center' }}>
|
<Flex
|
||||||
<Spin size='large' />
|
justify='center'
|
||||||
<div style={{ marginTop: 16 }}>
|
align='center'
|
||||||
<Text type='secondary'>Loading notifications...</Text>
|
style={{ padding: '40px' }}
|
||||||
</div>
|
gap='small'
|
||||||
</div>
|
>
|
||||||
|
<Spin indicator={<LoadingOutlined />} />
|
||||||
|
<Text type='secondary'>Loading notifications...</Text>
|
||||||
|
</Flex>
|
||||||
) : notifications.length === 0 ? (
|
) : notifications.length === 0 ? (
|
||||||
<Empty
|
<Flex justify='center' align='center' style={{ padding: '40px' }}>
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
<Empty
|
||||||
description='No notifications'
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
style={{ padding: '40px 20px' }}
|
description='No notifications'
|
||||||
/>
|
/>
|
||||||
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<Flex vertical gap='small' style={{ padding: '16px' }}>
|
<Flex vertical gap='small'>
|
||||||
{notifications.map((notification) => (
|
{notifications.map((notification) => (
|
||||||
<Notification
|
<Notification
|
||||||
key={notification._id}
|
key={notification._id}
|
||||||
notification={notification}
|
notification={notification}
|
||||||
onMarkAsRead={markAsRead}
|
onMarkAsRead={handleMarkAsRead}
|
||||||
|
onDelete={deleteNotification}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</div>
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
<Popconfirm
|
|
||||||
title='Delete all notifications?'
|
|
||||||
description='This action cannot be undone.'
|
|
||||||
open={showDeleteConfirm}
|
|
||||||
onConfirm={deleteAllNotifications}
|
|
||||||
onCancel={() => setShowDeleteConfirm(false)}
|
|
||||||
okText='Yes'
|
|
||||||
cancelText='No'
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ const TimeDisplay = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align={'center'} gap={'small'}>
|
<Flex align={'center'} gap={'small'}>
|
||||||
<Text type={type}>{formattedDate}</Text>
|
{showDate || showTime ? <Text type={type}>{formattedDate}</Text> : null}
|
||||||
{showSince ? <Tag style={{ margin: 0 }}>{timeAgo}</Tag> : null}
|
{showSince ? <Tag style={{ margin: 0 }}>{timeAgo}</Tag> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|||||||
232
src/components/Dashboard/common/UserNotifierToggle.jsx
Normal file
232
src/components/Dashboard/common/UserNotifierToggle.jsx
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { useState, useEffect, useContext } from 'react'
|
||||||
|
import { Button, message, Popover, Typography, Space, Flex } from 'antd'
|
||||||
|
import { UserOutlined } from '@ant-design/icons'
|
||||||
|
import BellIcon from '../../Icons/BellIcon'
|
||||||
|
import NewMailIcon from '../../Icons/NewMailIcon'
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
|
|
||||||
|
const { Text } = Typography
|
||||||
|
|
||||||
|
const UserNotifierToggle = ({
|
||||||
|
type,
|
||||||
|
objectData,
|
||||||
|
disabled = false,
|
||||||
|
...buttonProps
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
toggleUserNotifier,
|
||||||
|
editUserNotifier,
|
||||||
|
fetchUserNotifiersForObject,
|
||||||
|
fetchAllUserNotifiersForObject
|
||||||
|
} = useContext(ApiServerContext)
|
||||||
|
const { userProfile } = useContext(AuthContext)
|
||||||
|
const [isNotifying, setIsNotifying] = useState(false)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [initialLoad, setInitialLoad] = useState(true)
|
||||||
|
const [allNotifiers, setAllNotifiers] = useState([])
|
||||||
|
const [popoverOpen, setPopoverOpen] = useState(false)
|
||||||
|
const [popoverLoading, setPopoverLoading] = useState(false)
|
||||||
|
const [emailTogglingId, setEmailTogglingId] = useState(null)
|
||||||
|
|
||||||
|
const objectId = objectData?._id
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadNotifierState = async () => {
|
||||||
|
if (!objectId || !type) return
|
||||||
|
|
||||||
|
setInitialLoad(true)
|
||||||
|
try {
|
||||||
|
const { data } = await fetchUserNotifiersForObject(objectId, type)
|
||||||
|
setIsNotifying(data?.length > 0)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user notifier state:', error)
|
||||||
|
} finally {
|
||||||
|
setInitialLoad(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNotifierState()
|
||||||
|
}, [objectId, type, fetchUserNotifiersForObject])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadAllNotifiers = async () => {
|
||||||
|
if (!objectId || !type || !popoverOpen) return
|
||||||
|
|
||||||
|
setPopoverLoading(true)
|
||||||
|
try {
|
||||||
|
const { data } = await fetchAllUserNotifiersForObject(objectId, type)
|
||||||
|
setAllNotifiers(data || [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching all user notifiers:', error)
|
||||||
|
setAllNotifiers([])
|
||||||
|
} finally {
|
||||||
|
setPopoverLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllNotifiers()
|
||||||
|
}, [objectId, type, popoverOpen, fetchAllUserNotifiersForObject])
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
if (!objectId || !type || loading) return
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const enabled = await toggleUserNotifier(objectId, type)
|
||||||
|
setIsNotifying(enabled)
|
||||||
|
if (popoverOpen) {
|
||||||
|
const { data } = await fetchAllUserNotifiersForObject(objectId, type)
|
||||||
|
setAllNotifiers(data || [])
|
||||||
|
}
|
||||||
|
message.success(
|
||||||
|
enabled
|
||||||
|
? 'Notifications enabled for this object'
|
||||||
|
: 'Notifications disabled for this object'
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error toggling user notifier:', error)
|
||||||
|
message.error('Failed to update notifications')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserDisplayName = (user) => {
|
||||||
|
if (!user) return 'Unknown'
|
||||||
|
return (
|
||||||
|
user.name ||
|
||||||
|
user.username ||
|
||||||
|
`${user.firstName || ''} ${user.lastName || ''}`.trim() ||
|
||||||
|
user.email ||
|
||||||
|
'Unknown'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCurrentUser = (user) => user?._id === userProfile?._id
|
||||||
|
|
||||||
|
const handleEmailToggle = async (item) => {
|
||||||
|
if (!isCurrentUser(item.user) || emailTogglingId) return
|
||||||
|
setEmailTogglingId(item._id)
|
||||||
|
const newEmail = !item.email
|
||||||
|
try {
|
||||||
|
const result = await editUserNotifier(item._id, { email: newEmail })
|
||||||
|
if (result) {
|
||||||
|
setAllNotifiers((prev) =>
|
||||||
|
prev.map((n) =>
|
||||||
|
n._id === item._id ? { ...n, email: result.email ?? newEmail } : n
|
||||||
|
)
|
||||||
|
)
|
||||||
|
message.success(
|
||||||
|
(result.email ?? newEmail)
|
||||||
|
? 'Email notifications enabled'
|
||||||
|
: 'Email notifications disabled'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error toggling email:', error)
|
||||||
|
message.error('Failed to update email notifications')
|
||||||
|
} finally {
|
||||||
|
setEmailTogglingId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const popoverContent = (
|
||||||
|
<Flex
|
||||||
|
vertical
|
||||||
|
justify='center'
|
||||||
|
style={{ minWidth: 240, minHeight: 25 }}
|
||||||
|
gap={'4px'}
|
||||||
|
>
|
||||||
|
{popoverLoading ? (
|
||||||
|
<Space size={'small'}>
|
||||||
|
<LoadingOutlined />
|
||||||
|
<Text style={{ margin: 0 }}>Loading, please wait...</Text>
|
||||||
|
</Space>
|
||||||
|
) : allNotifiers.length === 0 ? (
|
||||||
|
<Space size={'small'}>
|
||||||
|
<Text style={{ margin: 0 }} type='secondary'>
|
||||||
|
<InfoCircleIcon />
|
||||||
|
</Text>
|
||||||
|
<Text style={{ margin: 0 }} type='secondary'>
|
||||||
|
No users subscribed.
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{[...allNotifiers]
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(isCurrentUser(b.user) ? 1 : 0) -
|
||||||
|
(isCurrentUser(a.user) ? 1 : 0)
|
||||||
|
)
|
||||||
|
.map((item) => (
|
||||||
|
<Flex key={item._id} justify='space-between' align='center'>
|
||||||
|
<Flex align='center' gap={'6px'}>
|
||||||
|
<UserOutlined />
|
||||||
|
<Text>{getUserDisplayName(item.user)}</Text>
|
||||||
|
{isCurrentUser(item.user) && (
|
||||||
|
<Text type='secondary'> (you)</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Space size={'small'}>
|
||||||
|
<Button
|
||||||
|
type='text'
|
||||||
|
icon={
|
||||||
|
<NewMailIcon
|
||||||
|
style={{
|
||||||
|
color: item.email ? 'var(--color-primary)' : undefined
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size='small'
|
||||||
|
disabled={!isCurrentUser(item.user)}
|
||||||
|
loading={emailTogglingId === item._id}
|
||||||
|
onClick={() => handleEmailToggle(item)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
content={popoverContent}
|
||||||
|
title={null}
|
||||||
|
trigger='hover'
|
||||||
|
placement='bottomLeft'
|
||||||
|
arrow={false}
|
||||||
|
open={popoverOpen}
|
||||||
|
onOpenChange={setPopoverOpen}
|
||||||
|
styles={{ body: { padding: '10px 15px' } }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
{...buttonProps}
|
||||||
|
icon={
|
||||||
|
<BellIcon
|
||||||
|
style={{
|
||||||
|
color: isNotifying ? 'var(--color-warning)' : undefined
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
disabled={disabled || loading || initialLoad}
|
||||||
|
loading={loading || !objectId || !type}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
UserNotifierToggle.propTypes = {
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
objectData: PropTypes.object.isRequired,
|
||||||
|
disabled: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserNotifierToggle
|
||||||
@ -36,6 +36,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const [retryCallback, setRetryCallback] = useState(null)
|
const [retryCallback, setRetryCallback] = useState(null)
|
||||||
const subscribedCallbacksRef = useRef(new Map())
|
const subscribedCallbacksRef = useRef(new Map())
|
||||||
const subscribedLockCallbacksRef = useRef(new Map())
|
const subscribedLockCallbacksRef = useRef(new Map())
|
||||||
|
const notificationListenersRef = useRef(new Set())
|
||||||
|
|
||||||
const handleLockUpdate = useCallback(
|
const handleLockUpdate = useCallback(
|
||||||
async (lockData) => {
|
async (lockData) => {
|
||||||
@ -72,6 +73,16 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const clearSubscriptions = useCallback(() => {
|
const clearSubscriptions = useCallback(() => {
|
||||||
subscribedCallbacksRef.current.clear()
|
subscribedCallbacksRef.current.clear()
|
||||||
subscribedLockCallbacksRef.current.clear()
|
subscribedLockCallbacksRef.current.clear()
|
||||||
|
notificationListenersRef.current.clear()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const registerNotificationListener = useCallback((callback) => {
|
||||||
|
notificationListenersRef.current.add(callback)
|
||||||
|
return () => notificationListenersRef.current.delete(callback)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const unregisterNotificationListener = useCallback((callback) => {
|
||||||
|
notificationListenersRef.current.delete(callback)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const connectToServer = useCallback(() => {
|
const connectToServer = useCallback(() => {
|
||||||
@ -101,6 +112,25 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
newSocket.on('objectDelete', handleObjectDelete)
|
newSocket.on('objectDelete', handleObjectDelete)
|
||||||
newSocket.on('lockUpdate', handleLockUpdate)
|
newSocket.on('lockUpdate', handleLockUpdate)
|
||||||
newSocket.on('modelStats', handleModelStats)
|
newSocket.on('modelStats', handleModelStats)
|
||||||
|
newSocket.on('notification', (data) => {
|
||||||
|
let notification
|
||||||
|
try {
|
||||||
|
notification =
|
||||||
|
typeof data === 'string' ? JSON.parse(data) : data
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to parse notification:', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!notification) return
|
||||||
|
logger.debug('Notification received:', notification)
|
||||||
|
notificationListenersRef.current.forEach((cb) => {
|
||||||
|
try {
|
||||||
|
cb(notification)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Error in notification callback:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
newSocket.on('disconnect', () => {
|
newSocket.on('disconnect', () => {
|
||||||
logger.debug('Api Server disconnected')
|
logger.debug('Api Server disconnected')
|
||||||
@ -1212,6 +1242,130 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createUserNotifier = async (objectId, objectType) => {
|
||||||
|
if (!userProfile?._id) return null
|
||||||
|
return createObject('userNotifier', {
|
||||||
|
user: userProfile._id,
|
||||||
|
object: objectId,
|
||||||
|
objectType
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteUserNotifier = async (userNotifierId) => {
|
||||||
|
return deleteObject(userNotifierId, 'userNotifier')
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchUserNotifiersForObject = async (objectId, objectType) => {
|
||||||
|
if (!userProfile?._id) return { data: [] }
|
||||||
|
const result = await fetchObjects('userNotifier', {
|
||||||
|
filter: {
|
||||||
|
user: userProfile._id,
|
||||||
|
object: objectId,
|
||||||
|
objectType
|
||||||
|
},
|
||||||
|
limit: 1
|
||||||
|
})
|
||||||
|
return result || { data: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchAllUserNotifiersForObject = async (objectId, objectType) => {
|
||||||
|
const result = await fetchObjects('userNotifier', {
|
||||||
|
filter: {
|
||||||
|
object: objectId,
|
||||||
|
objectType
|
||||||
|
},
|
||||||
|
limit: 100
|
||||||
|
})
|
||||||
|
return result || { data: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleUserNotifier = async (objectId, objectType) => {
|
||||||
|
const { data } = await fetchUserNotifiersForObject(objectId, objectType)
|
||||||
|
const existing = data?.[0]
|
||||||
|
if (existing) {
|
||||||
|
await deleteUserNotifier(existing._id)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
await createUserNotifier(objectId, objectType)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editUserNotifier = async (userNotifierId, updates) => {
|
||||||
|
try {
|
||||||
|
const result = await updateObject(userNotifierId, 'userNotifier', updates)
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => editUserNotifier(userNotifierId, updates))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchNotificationsApi = useCallback(async () => {
|
||||||
|
const response = await axios.get(`${config.backendUrl}/notifications`, {
|
||||||
|
params: { limit: 100, sort: 'createdAt', order: 'descend' },
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Array.isArray(response.data) ? response.data : []
|
||||||
|
}, [token])
|
||||||
|
|
||||||
|
const markNotificationAsReadApi = useCallback(
|
||||||
|
async (notificationId) => {
|
||||||
|
await axios.put(
|
||||||
|
`${config.backendUrl}/notifications/${notificationId}/read`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[token]
|
||||||
|
)
|
||||||
|
|
||||||
|
const markAllNotificationsAsReadApi = useCallback(async () => {
|
||||||
|
await axios.put(
|
||||||
|
`${config.backendUrl}/notifications/read-all`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}, [token])
|
||||||
|
|
||||||
|
const deleteNotificationApi = useCallback(
|
||||||
|
async (notificationId) => {
|
||||||
|
await axios.delete(
|
||||||
|
`${config.backendUrl}/notifications/${notificationId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[token]
|
||||||
|
)
|
||||||
|
|
||||||
|
const deleteAllNotificationsApi = useCallback(async () => {
|
||||||
|
await axios.delete(`${config.backendUrl}/notifications`, {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [token])
|
||||||
|
|
||||||
const flushFile = async (id) => {
|
const flushFile = async (id) => {
|
||||||
logger.debug('Flushing file...')
|
logger.debug('Flushing file...')
|
||||||
try {
|
try {
|
||||||
@ -1288,7 +1442,20 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
sendObjectAction,
|
sendObjectAction,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
flushFile,
|
flushFile,
|
||||||
formatFileName
|
formatFileName,
|
||||||
|
createUserNotifier,
|
||||||
|
deleteUserNotifier,
|
||||||
|
fetchUserNotifiersForObject,
|
||||||
|
fetchAllUserNotifiersForObject,
|
||||||
|
toggleUserNotifier,
|
||||||
|
editUserNotifier,
|
||||||
|
fetchNotificationsApi,
|
||||||
|
markNotificationAsReadApi,
|
||||||
|
markAllNotificationsAsReadApi,
|
||||||
|
deleteNotificationApi,
|
||||||
|
deleteAllNotificationsApi,
|
||||||
|
registerNotificationListener,
|
||||||
|
unregisterNotificationListener
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
|
|||||||
183
src/components/Dashboard/context/NotificationContext.jsx
Normal file
183
src/components/Dashboard/context/NotificationContext.jsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
useCallback,
|
||||||
|
useEffect
|
||||||
|
} from 'react'
|
||||||
|
import { notification, Drawer } from 'antd'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { AuthContext } from './AuthContext'
|
||||||
|
import { ApiServerContext } from './ApiServerContext'
|
||||||
|
import NotificationCenter from '../common/NotificationCenter'
|
||||||
|
import Notification from '../common/Notification'
|
||||||
|
|
||||||
|
const NotificationContext = createContext()
|
||||||
|
|
||||||
|
const NotificationProvider = ({ children }) => {
|
||||||
|
const [api, contextHolder] = notification.useNotification()
|
||||||
|
const { authenticated } = useContext(AuthContext)
|
||||||
|
const {
|
||||||
|
showError,
|
||||||
|
fetchNotificationsApi,
|
||||||
|
markNotificationAsReadApi,
|
||||||
|
markAllNotificationsAsReadApi,
|
||||||
|
deleteNotificationApi,
|
||||||
|
deleteAllNotificationsApi,
|
||||||
|
registerNotificationListener
|
||||||
|
} = useContext(ApiServerContext)
|
||||||
|
|
||||||
|
const [notificationCenterVisible, setNotificationCenterVisible] =
|
||||||
|
useState(false)
|
||||||
|
const [notifications, setNotifications] = useState([])
|
||||||
|
const [notificationsLoading, setNotificationsLoading] = useState(false)
|
||||||
|
|
||||||
|
const fetchNotifications = useCallback(async () => {
|
||||||
|
if (!authenticated) return []
|
||||||
|
setNotificationsLoading(true)
|
||||||
|
try {
|
||||||
|
const data = await fetchNotificationsApi()
|
||||||
|
setNotifications(data)
|
||||||
|
return data
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => fetchNotifications())
|
||||||
|
return []
|
||||||
|
} finally {
|
||||||
|
setNotificationsLoading(false)
|
||||||
|
}
|
||||||
|
}, [authenticated, fetchNotificationsApi, showError])
|
||||||
|
|
||||||
|
const markNotificationAsRead = useCallback(
|
||||||
|
async (notificationId) => {
|
||||||
|
try {
|
||||||
|
await markNotificationAsReadApi(notificationId)
|
||||||
|
setNotifications((prev) =>
|
||||||
|
prev.map((n) => (n._id === notificationId ? { ...n, read: true } : n))
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => markNotificationAsRead(notificationId))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[markNotificationAsReadApi, showError]
|
||||||
|
)
|
||||||
|
|
||||||
|
const markAllNotificationsAsRead = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await markAllNotificationsAsReadApi()
|
||||||
|
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => markAllNotificationsAsRead())
|
||||||
|
}
|
||||||
|
}, [markAllNotificationsAsReadApi, showError])
|
||||||
|
|
||||||
|
const deleteNotification = useCallback(
|
||||||
|
async (notificationId) => {
|
||||||
|
try {
|
||||||
|
await deleteNotificationApi(notificationId)
|
||||||
|
setNotifications((prev) => prev.filter((n) => n._id !== notificationId))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => deleteNotification(notificationId))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[deleteNotificationApi, showError]
|
||||||
|
)
|
||||||
|
|
||||||
|
const deleteAllNotifications = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await deleteAllNotificationsApi()
|
||||||
|
setNotifications([])
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showError(err, () => deleteAllNotifications())
|
||||||
|
}
|
||||||
|
}, [deleteAllNotificationsApi, showError])
|
||||||
|
|
||||||
|
const toggleNotificationCenter = useCallback(() => {
|
||||||
|
setNotificationCenterVisible((prev) => !prev)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const unreadCount = notifications.filter((n) => !n.read).length
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (authenticated && notificationCenterVisible) {
|
||||||
|
fetchNotifications()
|
||||||
|
}
|
||||||
|
}, [authenticated, notificationCenterVisible, fetchNotifications])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (authenticated) {
|
||||||
|
fetchNotifications()
|
||||||
|
}
|
||||||
|
}, [authenticated, fetchNotifications])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authenticated || !registerNotificationListener) return
|
||||||
|
const handleNotification = (notif) => {
|
||||||
|
setNotifications((prev) => {
|
||||||
|
const exists = prev.some((n) => n._id === notif._id)
|
||||||
|
if (exists) return prev
|
||||||
|
return [{ ...notif, read: false }, ...prev]
|
||||||
|
})
|
||||||
|
const show = api.open
|
||||||
|
show({
|
||||||
|
message: (
|
||||||
|
<Notification
|
||||||
|
notification={notif}
|
||||||
|
onDelete={deleteNotification}
|
||||||
|
showCard={false}
|
||||||
|
showDelete={false}
|
||||||
|
showExtraInfo={false}
|
||||||
|
inlineIcon={true}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
icon: null,
|
||||||
|
duration: 3,
|
||||||
|
key: notif._id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const unregister = registerNotificationListener(handleNotification)
|
||||||
|
return unregister
|
||||||
|
}, [authenticated, registerNotificationListener, api])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NotificationContext.Provider
|
||||||
|
value={{
|
||||||
|
notificationCenterVisible,
|
||||||
|
toggleNotificationCenter,
|
||||||
|
notifications,
|
||||||
|
fetchNotifications,
|
||||||
|
markNotificationAsRead,
|
||||||
|
markAllNotificationsAsRead,
|
||||||
|
deleteNotification,
|
||||||
|
deleteAllNotifications,
|
||||||
|
unreadCount,
|
||||||
|
notificationsLoading
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{contextHolder}
|
||||||
|
{children}
|
||||||
|
<Drawer
|
||||||
|
title='Notifications'
|
||||||
|
placement='right'
|
||||||
|
width={400}
|
||||||
|
onClose={() => setNotificationCenterVisible(false)}
|
||||||
|
open={notificationCenterVisible}
|
||||||
|
>
|
||||||
|
<NotificationCenter
|
||||||
|
visible={notificationCenterVisible}
|
||||||
|
onClose={() => setNotificationCenterVisible(false)}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
</NotificationContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationProvider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NotificationContext, NotificationProvider }
|
||||||
@ -100,6 +100,23 @@ export const ThemeProvider = ({ children }) => {
|
|||||||
return colors
|
return colors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set CSS custom properties for theme colors
|
||||||
|
useEffect(() => {
|
||||||
|
const root = document.documentElement
|
||||||
|
root.style.setProperty('--color-primary', colors.colorPrimary)
|
||||||
|
root.style.setProperty('--color-success', colors.colorSuccess)
|
||||||
|
root.style.setProperty('--color-warning', colors.colorWarning)
|
||||||
|
root.style.setProperty('--color-error', colors.colorError)
|
||||||
|
root.style.setProperty('--color-info', colors.colorInfo)
|
||||||
|
root.style.setProperty('--color-link', colors.colorLink)
|
||||||
|
root.style.setProperty('--color-cyan', colors.colorCyan)
|
||||||
|
root.style.setProperty('--color-pink', colors.colorPink)
|
||||||
|
root.style.setProperty('--color-purple', colors.colorPurple)
|
||||||
|
root.style.setProperty('--color-magenta', colors.colorMagenta)
|
||||||
|
root.style.setProperty('--color-volcano', colors.colorVolcano)
|
||||||
|
root.style.setProperty('--layout-header-bg', isDarkMode ? '#141414' : '#ffffff')
|
||||||
|
}, [isDarkMode])
|
||||||
|
|
||||||
const themeConfig = {
|
const themeConfig = {
|
||||||
algorithm: getThemeAlgorithm(),
|
algorithm: getThemeAlgorithm(),
|
||||||
token: {
|
token: {
|
||||||
|
|||||||
@ -158,26 +158,19 @@ export const Payment = {
|
|||||||
showHyperlink: true
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'useRemainingAmount',
|
name: 'payTo',
|
||||||
label: 'Use Remaining Amount',
|
label: 'Pay To',
|
||||||
type: 'boolean',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vendor',
|
|
||||||
label: 'Vendor',
|
|
||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: 'vendor',
|
objectType: 'vendor',
|
||||||
showHyperlink: true,
|
showHyperlink: true,
|
||||||
readOnly: true
|
required: true,
|
||||||
},
|
readOnly: true,
|
||||||
{
|
disabled: (objectData) => {
|
||||||
name: 'client',
|
return objectData?.invoice?.orderType == 'purchaseOrder'
|
||||||
label: 'Client',
|
},
|
||||||
type: 'object',
|
value: (objectData) => {
|
||||||
objectType: 'client',
|
return objectData?.invoice?.from
|
||||||
showHyperlink: true,
|
}
|
||||||
readOnly: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'paymentDate',
|
name: 'paymentDate',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user