Compare commits

...

3 Commits

42 changed files with 2137 additions and 360 deletions

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.887143,0,0,0.887143,3,3.211722)">
<path d="M37.605,64.798C35.97,65.05 34.296,65.181 32.59,65.181C14.599,65.181 0,50.582 0,32.59C0,14.599 14.599,0 32.59,0C50.592,0 65.191,14.599 65.191,32.59C65.191,32.92 65.186,33.248 65.176,33.575L64.728,33.251L64.711,33.241L63.625,32.757L62.474,32.491L61.304,32.455L60.16,32.642L59.079,33.04L58.094,33.629L58.006,33.706C58.022,33.336 58.03,32.964 58.03,32.59C58.03,18.529 46.652,7.161 32.59,7.161C18.529,7.161 7.161,18.529 7.161,32.59C7.161,46.652 18.529,58.02 32.59,58.02C36.258,58.02 39.743,57.247 42.892,55.854L38.593,62.35C38.366,62.692 38.175,63.045 38.019,63.4L37.639,64.54L37.605,64.798Z"/>
</g>
<g transform="matrix(0.887143,0,0,0.887143,3,3.211722)">
<path d="M33.627,46.224L22.957,46.224C21.791,46.224 20.916,45.429 20.916,44.203C20.916,43.087 21.687,42.386 22.622,41.935C24.986,40.884 26.28,38.794 26.28,36.193C26.28,35.474 26.152,34.744 25.961,33.93L22.25,33.93C21.359,33.93 20.726,33.348 20.726,32.529C20.726,31.742 21.359,31.16 22.25,31.16L25.284,31.16C24.896,29.781 24.7,28.625 24.7,27.447C24.7,21.845 29.228,18.881 35.049,18.881C37.066,18.881 38.335,19.006 39.843,19.534C41.021,19.845 41.964,20.474 41.964,21.695C41.964,22.719 41.283,23.327 40.22,23.327C39.785,23.327 39.274,23.232 38.771,23.127C38.008,22.948 36.92,22.811 35.732,22.811C32.123,22.811 29.593,24.555 29.593,27.791C29.593,28.786 29.707,29.656 30.074,31.16L38.186,31.16C39.066,31.16 39.71,31.753 39.71,32.529C39.71,33.338 39.066,33.93 38.186,33.93L30.783,33.93C30.911,34.534 30.975,35.213 30.975,36.008C30.975,38.529 29.988,41.074 27.758,42.081L27.758,42.226L33.619,42.226C33.535,42.851 33.491,43.509 33.491,44.2C33.491,44.907 33.538,45.582 33.627,46.224Z"/>
</g>
<g transform="matrix(0.48434,0,0,0.48434,31.429473,34.991997)">
<path d="M20.943,29.037C28.325,29.037 33.046,23.873 33.046,15.317C33.046,6.658 28.272,1.806 20.943,1.806C13.64,1.806 8.841,6.658 8.841,15.343C8.841,23.925 13.588,29.037 20.943,29.037ZM20.943,22.073C18.883,22.073 17.918,19.908 17.918,15.343C17.918,10.831 18.883,8.744 20.943,8.744C23.004,8.744 23.969,10.831 23.969,15.343C23.969,19.908 23.004,22.073 20.943,22.073ZM55.145,57.616C62.526,57.616 67.247,52.478 67.247,43.923C67.247,35.263 62.474,30.386 55.145,30.386C47.868,30.386 43.042,35.263 43.042,43.949C43.042,52.53 47.789,57.616 55.145,57.616ZM55.145,50.678C53.084,50.678 52.119,48.513 52.119,43.949C52.119,39.436 53.084,37.35 55.145,37.35C57.205,37.35 58.17,39.436 58.17,43.949C58.17,48.513 57.205,50.678 55.145,50.678ZM18.469,59.162C20.812,60.537 23.938,59.943 25.469,57.599L58.25,7.881C59.938,5.318 59.344,2.131 56.781,0.662C54.406,-0.682 51.562,0.068 49.938,2.475L17.156,52.006C15.438,54.599 15.906,57.662 18.469,59.162Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.887143,0,0,0.887143,3,3.211722)">
<path d="M29.615,65.047C13.017,63.543 0,49.579 0,32.59C0,14.599 14.599,0 32.59,0C50.592,0 65.191,14.599 65.191,32.59C65.191,36.04 64.654,39.365 63.66,42.486C63.098,42.436 62.495,42.411 61.843,42.411L56.068,42.411C57.332,39.391 58.03,36.073 58.03,32.59C58.03,18.529 46.652,7.161 32.59,7.161C18.529,7.161 7.161,18.529 7.161,32.59C7.161,45.539 16.801,56.204 29.308,57.811L29.308,61.604C29.308,62.941 29.41,64.07 29.615,65.047Z"/>
</g>
<g transform="matrix(0.887143,0,0,0.887143,3,3.211722)">
<path d="M30.87,46.224L22.957,46.224C21.791,46.224 20.916,45.429 20.916,44.203C20.916,43.087 21.687,42.386 22.622,41.935C24.986,40.884 26.28,38.794 26.28,36.193C26.28,35.474 26.152,34.744 25.961,33.93L22.25,33.93C21.359,33.93 20.726,33.348 20.726,32.529C20.726,31.742 21.359,31.16 22.25,31.16L25.284,31.16C24.896,29.781 24.7,28.625 24.7,27.447C24.7,21.845 29.228,18.881 35.049,18.881C37.066,18.881 38.335,19.006 39.843,19.534C41.021,19.845 41.964,20.474 41.964,21.695C41.964,22.719 41.283,23.327 40.22,23.327C39.785,23.327 39.274,23.232 38.771,23.127C38.008,22.948 36.92,22.811 35.732,22.811C32.123,22.811 29.593,24.555 29.593,27.791C29.593,28.786 29.707,29.656 30.074,31.16L38.186,31.16C39.066,31.16 39.71,31.753 39.71,32.529C39.71,33.338 39.066,33.93 38.186,33.93L30.783,33.93C30.911,34.534 30.975,35.213 30.975,36.008C30.975,38.529 29.988,41.074 27.758,42.081L27.758,42.226L35.078,42.226C35.08,42.226 35.082,42.226 35.084,42.226C35.222,42.228 35.338,42.331 35.355,42.468C35.373,42.605 35.287,42.734 35.154,42.771C34.259,43.164 32.987,43.909 31.885,45.011C31.503,45.392 31.165,45.792 30.87,46.224Z"/>
</g>
<g transform="matrix(0.317224,0,0,0.317224,32,43.836431)">
<path d="M19.344,63.562L81.531,63.562C88.781,63.562 93.094,62.406 96.438,59.062C99.75,55.75 100.875,51.562 100.875,44.219L100.875,19.344C100.875,12 99.75,7.781 96.438,4.5C93.062,1.156 88.781,0 81.531,0L19.344,0C12.094,0 7.781,1.156 4.438,4.5C1.125,7.812 0,12 0,19.344L0,44.219C0,51.562 1.125,55.781 4.438,59.062C7.812,62.406 12.094,63.562 19.344,63.562ZM18.469,52.469C15.125,52.469 13.656,51.969 12.594,50.938C11.562,49.844 11.094,48.469 11.094,45.094L11.094,18.469C11.094,15.094 11.562,13.688 12.594,12.625C13.656,11.594 15.125,11.094 18.469,11.094L82.406,11.094C85.75,11.094 87.219,11.594 88.281,12.625C89.312,13.688 89.781,15.094 89.781,18.469L89.781,45.094C89.781,48.469 89.312,49.844 88.281,50.938C87.219,51.969 85.75,52.469 82.406,52.469L18.469,52.469Z" style="fill-rule:nonzero;"/>
<path d="M20.531,30.188L28.031,30.188C30.531,30.188 32.25,28.469 32.25,25.969L32.25,20.469C32.25,17.969 30.531,16.25 28.031,16.25L20.531,16.25C18.031,16.25 16.312,17.969 16.312,20.469L16.312,25.969C16.312,28.469 18.031,30.188 20.531,30.188ZM18.75,47.281L82.125,47.281C83.469,47.281 84.531,46.156 84.531,44.844C84.531,43.5 83.469,42.469 82.125,42.469L18.75,42.469C17.406,42.469 16.344,43.5 16.344,44.844C16.344,46.156 17.406,47.281 18.75,47.281Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.887143,0,0,0.887143,3,3.211722)">
<path d="M40.292,64.266C37.823,64.864 35.243,65.181 32.59,65.181C14.599,65.181 0,50.582 0,32.59C0,14.599 14.599,0 32.59,0C48.085,0 61.059,10.816 64.372,25.302C62.551,23.444 59.987,22.309 57.084,22.306L57.08,22.306C56.676,22.306 56.281,22.325 55.894,22.364C51.96,13.406 43.012,7.161 32.59,7.161C18.529,7.161 7.161,18.529 7.161,32.59C7.161,46.652 18.529,58.02 32.59,58.02C35.805,58.02 38.88,57.426 41.71,56.341C40.562,58.022 40.04,59.878 40.04,61.96C40.04,62.761 40.127,63.532 40.292,64.266Z"/>
</g>
<g transform="matrix(0.887143,0,0,0.887143,3,3.211722)">
<path d="M35.033,46.224L22.957,46.224C21.791,46.224 20.916,45.429 20.916,44.203C20.916,43.087 21.687,42.386 22.622,41.935C24.986,40.884 26.28,38.794 26.28,36.193C26.28,35.474 26.152,34.744 25.961,33.93L22.25,33.93C21.359,33.93 20.726,33.348 20.726,32.529C20.726,31.742 21.359,31.16 22.25,31.16L25.284,31.16C24.896,29.781 24.7,28.625 24.7,27.447C24.7,21.845 29.228,18.881 35.049,18.881C37.066,18.881 38.335,19.006 39.843,19.534C41.021,19.845 41.964,20.474 41.964,21.695C41.964,22.719 41.283,23.327 40.22,23.327C39.785,23.327 39.274,23.232 38.771,23.127C38.008,22.948 36.92,22.811 35.732,22.811C32.123,22.811 29.593,24.555 29.593,27.791C29.593,28.786 29.707,29.656 30.074,31.16L38.186,31.16C39.066,31.16 39.71,31.753 39.71,32.529C39.71,33.338 39.066,33.93 38.186,33.93L30.783,33.93C30.911,34.534 30.975,35.213 30.975,36.008C30.975,38.529 29.988,41.074 27.758,42.081L27.758,42.226L35.079,42.226C34.947,42.877 34.876,43.548 34.87,44.233C34.863,44.909 34.917,45.574 35.033,46.224Z"/>
</g>
<g transform="matrix(0.465962,0,0,0.465962,36.933857,26)">
<path d="M0.001,35.371C-0.038,39.202 1.473,42.727 4.441,45.671L16.349,57.61C11.913,60.839 9.844,64.493 9.844,69.059C9.844,76.236 15.431,81.432 22.42,81.549C26.022,81.612 29.343,80.305 31.751,77.938L53.883,55.695C59.804,49.751 59.453,42.361 52.939,35.83L41.879,24.794L44.775,21.89C47.234,19.407 48.675,16.156 48.692,12.936C48.785,5.665 43.16,0.007 35.85,0C31.88,0 28.667,1.489 25.293,4.83L3.649,26.387C1.324,28.753 0.032,31.925 0.001,35.371ZM18.613,24.255L31.665,11.203C33.369,9.474 34.298,9.018 35.85,9.018C37.991,9.018 39.664,10.692 39.664,12.825C39.664,13.85 39.304,14.61 38.379,15.518L24.077,29.836C23.14,27.191 21.326,25.315 18.613,24.255ZM9.135,35.594C9.135,33.425 10.709,31.799 12.756,31.799C14.857,31.799 16.468,33.464 16.468,35.669C16.468,36.68 16.444,37.157 16.216,37.941C15.03,43.335 21.775,44.881 24.421,42.268L35.475,31.174L46.542,42.227C49.615,45.227 49.817,47.01 47.511,49.299L33.566,63.268C32.715,61.558 31.327,59.892 29.126,57.643L11.994,40.417C9.701,38.148 9.135,37.157 9.135,35.594ZM18.887,69.149C18.887,67.295 20.04,65.793 22.846,64.06L23.303,64.516C25.504,66.773 26.164,67.845 26.172,69.173C26.22,71.078 24.636,72.476 22.444,72.476C20.445,72.476 18.887,71.13 18.887,69.149Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -347,6 +347,26 @@ body {
}
.ant-select-selection-item .ant-tag,
.ant-select-tree-title .ant-tag {
.ant-select-tree-title .object-display-tag {
background: transparent !important;
padding: 0;
margin-right: 1px !important;
padding-top: 3px;
}
.ant-select-outlined.ant-select-multiple
.ant-select-selection-item
.object-display-tag {
padding-top: 0px;
}
.ant-select-outlined.ant-select-multiple
.ant-select-selection-item
.object-display-tag
.ant-flex {
margin-top: -0.75px;
}
.object-info-descriptions table {
table-layout: fixed !important;
}

View File

@ -19,6 +19,8 @@ import DocumentJobIcon from '../../Icons/DocumentJobIcon'
import FileIcon from '../../Icons/FileIcon'
import CourierIcon from '../../Icons/CourierIcon'
import CourierServiceIcon from '../../Icons/CourierServiceIcon'
import TaxRateIcon from '../../Icons/TaxRateIcon'
import TaxRecordIcon from '../../Icons/TaxRecordIcon'
const items = [
{
@ -65,6 +67,19 @@ const items = [
path: '/dashboard/management/courierservices'
},
{ type: 'divider' },
{
key: 'taxRates',
icon: <TaxRateIcon />,
label: 'Tax Rates',
path: '/dashboard/management/taxrates'
},
{
key: 'taxRecords',
icon: <TaxRecordIcon />,
label: 'Tax Records',
path: '/dashboard/management/taxrecords'
},
{ type: 'divider' },
{
key: 'noteTypes',
icon: <NoteTypeIcon />,
@ -156,6 +171,8 @@ const routeKeyMap = {
'/dashboard/management/vendors': 'vendors',
'/dashboard/management/couriers': 'couriers',
'/dashboard/management/courierservices': 'courierServices',
'/dashboard/management/taxrates': 'taxRates',
'/dashboard/management/taxrecords': 'taxRecords',
'/dashboard/management/materials': 'materials',
'/dashboard/management/notetypes': 'noteTypes',
'/dashboard/management/settings': 'settings',

View File

@ -25,8 +25,12 @@ const NewPart = ({ onOk }) => {
visibleProperties={{
file: false,
priceMode: false,
globalPricing: false,
amount: false,
cost: false,
costTaxRate: false,
costWithTax: false,
price: false,
priceTaxRate: false,
priceWithTax: false,
margin: false
}}
/>
@ -45,9 +49,13 @@ const NewPart = ({ onOk }) => {
objectData={objectData}
visibleProperties={{
priceMode: true,
globalPricing: true,
amount: true,
margin: true
margin: true,
cost: true,
costTaxRate: true,
costWithTax: true,
price: true,
priceTaxRate: true,
priceWithTax: true
}}
/>
)

View File

@ -0,0 +1,97 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import NewTaxRate from './TaxRates/NewTaxRate'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const TaxRates = () => {
const [messageApi, contextHolder] = message.useMessage()
const [newTaxRateOpen, setNewTaxRateOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('taxRate')
const [columnVisibility, setColumnVisibility] = useColumnVisibility('taxRate')
const actionItems = {
items: [
{
label: 'New Tax Rate',
key: 'newTaxRate',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newTaxRate') {
setNewTaxRateOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
{contextHolder}
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='taxRate'
loading={false}
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='taxRate'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newTaxRateOpen}
onCancel={() => setNewTaxRateOpen(false)}
footer={null}
destroyOnHidden={true}
width={700}
>
<NewTaxRate
onOk={() => {
setNewTaxRateOpen(false)
messageApi.success('New tax rate created successfully.')
tableRef.current?.reload()
}}
reset={!newTaxRateOpen}
/>
</Modal>
</>
)
}
export default TaxRates

View File

@ -0,0 +1,80 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewTaxRate = ({ onOk }) => {
return (
<NewObjectForm type={'taxRate'}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='taxRate'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='taxRate'
column={1}
bordered={false}
isEditing={true}
required={false}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='taxRate'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Tax Rate'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewTaxRate.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
}
export default NewTaxRate

View File

@ -0,0 +1,193 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import loglevel from 'loglevel'
import config from '../../../../config'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('TaxRateInfo')
log.setLevel(config.logLevel)
const TaxRateInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const taxRateId = new URLSearchParams(location.search).get('taxRateId')
const [collapseState, updateCollapseState] = useCollapseState('TaxRateInfo', {
info: true,
notes: true,
auditLogs: true
})
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='taxRate'
id={taxRateId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Tax Rate Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<DocumentPrintButton
type='taxRate'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Tax Rate Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={taxRateId}
type='taxRate'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='taxRate'
objectData={objectData}
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={taxRateId} type='taxRate' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': taxRateId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default TaxRateInfo

View File

@ -0,0 +1,98 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import NewTaxRecord from './TaxRecords/NewTaxRecord'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const TaxRecords = () => {
const [messageApi, contextHolder] = message.useMessage()
const [newTaxRecordOpen, setNewTaxRecordOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('taxRecord')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('taxRecord')
const actionItems = {
items: [
{
label: 'New Tax Record',
key: 'newTaxRecord',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newTaxRecord') {
setNewTaxRecordOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
{contextHolder}
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='taxRecord'
loading={false}
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='taxRecord'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newTaxRecordOpen}
onCancel={() => setNewTaxRecordOpen(false)}
footer={null}
destroyOnHidden={true}
width={700}
>
<NewTaxRecord
onOk={() => {
setNewTaxRecordOpen(false)
messageApi.success('New tax record created successfully.')
tableRef.current?.reload()
}}
reset={!newTaxRecordOpen}
/>
</Modal>
</>
)
}
export default TaxRecords

View File

@ -0,0 +1,80 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewTaxRecord = ({ onOk }) => {
return (
<NewObjectForm type={'taxRecord'}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='taxRecord'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='taxRecord'
column={1}
bordered={false}
isEditing={true}
required={false}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='taxRecord'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Tax Record'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewTaxRecord.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
}
export default NewTaxRecord

View File

@ -0,0 +1,196 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import loglevel from 'loglevel'
import config from '../../../../config'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('TaxRecordInfo')
log.setLevel(config.logLevel)
const TaxRecordInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const taxRecordId = new URLSearchParams(location.search).get('taxRecordId')
const [collapseState, updateCollapseState] = useCollapseState(
'TaxRecordInfo',
{
info: true,
notes: true,
auditLogs: true
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='taxRecord'
id={taxRecordId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Tax Record Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<DocumentPrintButton
type='taxRecord'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Tax Record Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={taxRecordId}
type='taxRecord'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='taxRecord'
objectData={objectData}
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={taxRecordId} type='taxRecord' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': taxRecordId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default TaxRecordInfo

View File

@ -172,6 +172,7 @@ const GCodeFileInfo = () => {
visibleProperties={{
parts: false
}}
labelWidth='200px'
/>
</InfoCollapse>
<InfoCollapse

View File

@ -19,7 +19,7 @@ import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel.jsx'
import PrinterPositionPanel from '../../common/PrinterPositionPanel.jsx'
import PrinterMovementPanel from '../../common/PrinterMovementPanel.jsx'
import PrinterMiscPanel from '../../common/PrinterMiscPanel.jsx'
import JobIcon from '../../../Icons/JobIcon.jsx'
import SubJobIcon from '../../../Icons/SubJobIcon.jsx'
import FilamentStockIcon from '../../../Icons/FilamentStockIcon.jsx'
@ -50,7 +50,8 @@ const ControlPrinter = () => {
filamentStock: true,
temperature: true,
position: true,
movement: true
movement: true,
misc: true
}
)
@ -58,7 +59,8 @@ const ControlPrinter = () => {
const [sideBarVisible, setSideBarVisible] = useState(
collapseState.temperature ||
collapseState.position ||
collapseState.movement
collapseState.movement ||
collapseState.misc
)
const [loadFilamentStockOpen, setLoadFilamentStockOpen] = useState(false)
@ -68,7 +70,8 @@ const ControlPrinter = () => {
setSideBarVisible(
collapseState.temperature ||
collapseState.position ||
collapseState.movement
collapseState.movement ||
collapseState.misc
)
}, [collapseState])
@ -168,18 +171,43 @@ const ControlPrinter = () => {
const sideBarItems = (
<Flex gap={isMobile ? 'large' : 'middle'} vertical>
{collapseState.temperature && (
<Card style={{ width: '100%' }}>
<Card
style={{ width: '100%' }}
title='Temperature'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterTemperaturePanel id={printerId} />
</Card>
)}
{collapseState.position && (
<Card style={{ width: '100%' }}>
<PrinterPositionPanel />
<Card
style={{ width: '100%' }}
title='Position'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterPositionPanel id={printerId} />
</Card>
)}
{collapseState.movement && (
<Card style={{ width: '100%' }}>
<PrinterMovementPanel />
<Card
style={{ width: '100%' }}
title='Movement'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterMovementPanel id={printerId} />
</Card>
)}
{collapseState.misc && (
<Card
style={{ width: '100%' }}
title='Misc'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterMiscPanel id={printerId} />
</Card>
)}
</Flex>
@ -236,6 +264,10 @@ const ControlPrinter = () => {
key: 'movement',
label: 'Movement'
},
{
key: 'misc',
label: 'Misc'
},
{ key: 'notes', label: 'Notes' }
]}
visibleState={collapseState}
@ -330,6 +362,7 @@ const ControlPrinter = () => {
}}
objectData={printerObjectData}
type='printer'
labelWidth='100px'
/>
)
}}
@ -361,7 +394,8 @@ const ControlPrinter = () => {
showHyperlink={true}
visibleProperties={{
printers: false,
createdAt: false
createdAt: false,
finishedAt: false
}}
objectData={jobObjectData}
type='job'
@ -402,7 +436,8 @@ const ControlPrinter = () => {
showHyperlink={true}
visibleProperties={{
printers: false,
createdAt: false
createdAt: false,
finishedAt: false
}}
objectData={subJobObjectData}
type='subJob'

View File

@ -142,6 +142,7 @@ const SubJobInfo = () => {
isEditing={isEditing}
type='subJob'
objectData={objectData}
labelWidth='175px'
/>
)}
</ObjectForm>

View File

@ -62,14 +62,17 @@ const FileList = ({
const filesToRender = multiple ? files : [files]
const renderFileContent = (file) => (
<Flex vertical gap='10px'>
<Flex justify={card ? 'space-between' : 'start'}>
<Flex gap={'small'} align='center'>
<Flex vertical gap='10px' style={{ maxWidth: '100%', minWidth: 0 }}>
<Flex
justify={card ? 'space-between' : 'start'}
style={{ maxWidth: '100%', minWidth: 0 }}
>
<Flex gap={'small'} align='center' style={{ minWidth: 0 }}>
<FileIcon
style={{ margin: 0, fontSize: card == true ? '24px' : '16px' }}
/>
<Text style={{ marginTop: '1px' }}>
<Text style={{ marginTop: '1px', minWidth: 0 }} ellipsis>
{file.name || file.filename || 'Unknown file'}
</Text>
<Tag>{file.extension}</Tag>

View File

@ -90,7 +90,6 @@ const NotesPanel = ({ _id, type }) => {
)
const handleReloadData = useCallback(async () => {
console.log('GOT RELOAD DATA')
setNotes(await generateNotes(_id))
}, [_id, generateNotes])
@ -100,7 +99,6 @@ const NotesPanel = ({ _id, type }) => {
'note',
(noteData) => {
if (noteData.parent._id == _id) {
console.log('Note note added to parent:', _id)
handleReloadData()
}
}
@ -115,11 +113,11 @@ const NotesPanel = ({ _id, type }) => {
}, [_id, subscribeToObjectTypeUpdates, connected, handleReloadData])
useEffect(() => {
if (token != null && !initialized) {
if (connected == true && token != null && !initialized) {
handleReloadData()
setInitialized(true)
}
}, [token, handleReloadData, initialized])
}, [token, handleReloadData, initialized, connected])
const actionItems = {
items: [

View File

@ -4,6 +4,7 @@ import { Table, Skeleton, Card, Button, Flex, Form, Typography } from 'antd'
import PlusIcon from '../../Icons/PlusIcon'
import ObjectProperty from './ObjectProperty'
import { LoadingOutlined } from '@ant-design/icons'
import BinIcon from '../../Icons/BinIcon'
const { Text } = Typography
const DEFAULT_COLUMN_WIDTHS = {
@ -135,8 +136,70 @@ const ObjectChildTable = ({
}
}))
return [...propertyColumns, ...additionalColumns]
}, [resolvedProperties, additionalColumns, isEditing])
const deleteColumn = isEditing
? {
title: '',
key: 'delete',
width: 10,
fixed: 'right',
render: (_text, record, index) => {
if (record?.isSkeleton) {
return null
}
return (
<Button
type='text'
danger
size='small'
icon={<BinIcon />}
onClick={(e) => {
e.stopPropagation()
const currentItems = Array.isArray(itemsSource)
? itemsSource
: []
// Use record's unique identifier if available, otherwise use index
let newItems
if (typeof rowKey === 'string' && record[rowKey] != null) {
// Use the unique key to find and remove the item
newItems = currentItems.filter(
(item) => item[rowKey] !== record[rowKey]
)
} else if (typeof rowKey === 'function') {
// If rowKey is a function, find the item by comparing the resolved keys
const recordKey = rowKey(record, index)
newItems = currentItems.filter((item, i) => {
const itemKey = rowKey(item, i)
return itemKey !== recordKey
})
} else {
// Fallback to index-based removal
newItems = currentItems.filter((_, i) => i !== index)
}
if (typeof onChange === 'function') {
onChange(newItems)
}
}}
/>
)
}
}
: null
return [
...propertyColumns,
...additionalColumns,
...(deleteColumn ? [deleteColumn] : [])
]
}, [
resolvedProperties,
additionalColumns,
isEditing,
itemsSource,
onChange,
rowKey
])
const skeletonData = useMemo(() => {
return createSkeletonRows(
@ -213,7 +276,7 @@ const ObjectChildTable = ({
}, [properties, rollups, objectData])
const rollupColumns = useMemo(() => {
return properties.map((property, index) => {
const propertyColumns = properties.map((property, index) => {
const nextProperty = properties[index + 1]
var nextRollup = null
if (nextProperty) {
@ -231,9 +294,14 @@ const ObjectChildTable = ({
return (
<Flex justify={'space-between'}>
<Text>
{record[property.name] !== undefined &&
record[property.name] !== null && (
<>
{property?.prefix}
{record[property.name]}
{property?.suffix}
</>
)}
</Text>
{rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
</Flex>
@ -241,7 +309,22 @@ const ObjectChildTable = ({
}
}
})
}, [properties, rollups])
const blankDeleteColumn = isEditing
? {
title: '',
key: 'delete',
width: 40,
fixed: 'right',
render: () => {
return <Flex></Flex>
}
}
: null
return [
...propertyColumns,
...(blankDeleteColumn ? [blankDeleteColumn] : [])
]
}, [properties, rollups, isEditing])
const hasRollups = useMemo(
() => Array.isArray(rollups) && rollups.length > 0,
@ -339,7 +422,7 @@ const ObjectChildTable = ({
if (isEditing === true && formListName) {
return (
<Form.List name={formListName}>
{(fields, { add }) => {
{(fields, { add, remove }) => {
const listDataSource = fields.map((field, index) => ({
_field: field,
_index: index,
@ -373,12 +456,38 @@ const ObjectChildTable = ({
}
}))
const deleteColumn = {
title: '',
key: 'delete',
width: 10,
fixed: 'right',
render: (_text, record) => {
const field = record?._field
const index = record?._index
if (!field || index == null) return null
return (
<Button
type='text'
danger
size='small'
icon={<BinIcon />}
onClick={(e) => {
e.stopPropagation()
if (typeof remove === 'function') {
remove(index)
}
}}
/>
)
}
}
const listTable = (
<Flex vertical>
<div ref={mainTableWrapperRef}>
<Table
dataSource={listDataSource}
columns={[...listColumns, ...additionalColumns]}
columns={[...listColumns, ...additionalColumns, deleteColumn]}
pagination={false}
size={size}
loading={loading}

View File

@ -53,13 +53,30 @@ const ObjectDisplay = ({ object, objectType }) => {
const model = getModelByName(objectType)
const Icon = model.icon
return (
<Tag style={{ margin: 0, border: 'none' }}>
<Flex gap={objectData?.color ? 'small' : '3px'} align='center'>
<Icon />
<Flex gap={'small'} align='center'>
<Tag
style={{
margin: 0,
border: 'none',
minWidth: 0,
paddingBottom: '2.5px',
maxWidth: '100%'
}}
className='object-display-tag'
>
<Flex
gap={objectData?.color ? 'small' : '4px'}
align='center'
style={{ minWidth: 0 }}
>
<Icon style={{ marginBottom: '-3px' }} />
<Flex gap={'small'} align='center' style={{ minWidth: 0 }}>
{objectData?.color ? <Badge color={objectData?.color} /> : null}
<div style={{ paddingTop: '1.5px' }}>
{objectData?.name ? <Text ellipsis>{objectData.name}</Text> : null}
<div style={{ paddingTop: '1.5px', minWidth: 0 }}>
{objectData?.name ? (
<Text ellipsis style={{ lineHeight: '1' }}>
{objectData.name}
</Text>
) : null}
{objectData?._id && !objectData?.name ? (
<IdDisplay
id={objectData?._id}

View File

@ -310,11 +310,11 @@ const ObjectForm = forwardRef(
}, [])
useEffect(() => {
if (initialized == false && id && token != null) {
if (connected == true && initialized == false && id && token != null) {
setInitialized(true)
handleFetchObject()
}
}, [id, initialized, handleFetchObject, token])
}, [id, initialized, handleFetchObject, token, connected])
useEffect(() => {
if (id && connected) {
@ -391,15 +391,19 @@ const ObjectForm = forwardRef(
try {
const value = await form.validateFields()
setEditLoading(true)
onStateChangeRef.current({ editLoading: true })
await updateObject(id, type, value)
const currentFormData = {
...value,
...objectData
}
onStateChangeRef.current({ editLoading: true })
await updateObject(id, type, currentFormData)
setIsEditing(false)
isEditingRef.current = false
onStateChangeRef.current({ isEditing: isEditingRef.current })
setObjectData({
...objectData,
...value,
...currentFormData,
_isEditing: isEditingRef.current
})
messageApi.success('Information updated successfully')

View File

@ -17,6 +17,7 @@ const ObjectInfo = ({
required = undefined,
visibleProperties = {},
objectPropertyProps = {},
labelWidth = '150px',
column = {
xs: 1,
sm: 1,
@ -86,6 +87,7 @@ const ObjectInfo = ({
</Flex>
) : null,
children: (
<div style={{ maxWidth: '100%', minWidth: 0, width: '100%' }}>
<ObjectProperty
{...item}
{...objectPropertyProps}
@ -93,6 +95,7 @@ const ObjectInfo = ({
objectData={combinedObjectData}
showSince={true}
/>
</div>
),
span: item?.span || undefined
}
@ -105,6 +108,8 @@ const ObjectInfo = ({
bordered={true}
{...rest}
items={descriptionItems}
styles={{ label: { width: labelWidth } }}
className='object-info-descriptions'
/>
</Spin>
)
@ -124,7 +129,8 @@ ObjectInfo.propTypes = {
objectData: PropTypes.object,
required: PropTypes.bool,
visibleProperties: PropTypes.object,
objectPropertyProps: PropTypes.object
objectPropertyProps: PropTypes.object,
labelWidth: PropTypes.string
}
export default ObjectInfo

View File

@ -45,6 +45,7 @@ import DataTree from './DataTree'
import FileList from './FileList'
import ObjectChildTable from './ObjectChildTable'
import MiscId from './MiscId'
import { round } from '../utils/Utils'
const { Text } = Typography
@ -265,7 +266,7 @@ const ObjectProperty = ({
} else {
var roundedValue = value
if (roundNumber != false && typeof value === 'number') {
roundedValue = value.toFixed(roundNumber)
roundedValue = round(value, roundNumber)
}
return (

View File

@ -35,7 +35,8 @@ const ObjectSelect = ({
disabled = false,
...rest
}) => {
const { fetchObjectsByProperty, fetchObject } = useContext(ApiServerContext)
const { fetchObjectsByProperty, fetchObject, connected } =
useContext(ApiServerContext)
const { token } = useContext(AuthContext)
// --- State ---
const [treeData, setTreeData] = useState([])
@ -373,11 +374,22 @@ const ObjectSelect = ({
setInitialized(true)
return
}
if (!initialized && token != null && type != 'unknown') {
if (
!initialized &&
token != null &&
type != 'unknown' &&
type != undefined &&
connected == true
) {
handleFetchObjectsProperties()
setInitialized(true)
}
if (value == null || type == 'unknown') {
if (
value == null ||
type == 'unknown' ||
type == undefined ||
connected == false
) {
setTreeSelectValue(null)
setInitialLoading(false)
setInitialized(true)
@ -392,7 +404,8 @@ const ObjectSelect = ({
initialized,
token,
fetchFullObjectIfNeeded,
type
type,
connected
])
const prevValuesRef = useRef({ type, masterFilter })
@ -443,7 +456,7 @@ const ObjectSelect = ({
const placeholder = useMemo(
() =>
type == 'unknown'
type == 'unknown' || type == undefined
? 'n/a'
: `Select a ${getModelByName(type).label.toLowerCase()}...`,
[type]
@ -486,7 +499,7 @@ const ObjectSelect = ({
{...rest}
value={treeSelectValue}
onChange={onTreeSelectChange}
disabled={disabled || type == 'unknown'}
disabled={disabled || type == 'unknown' || type == undefined}
/>
)
}

View File

@ -458,11 +458,16 @@ const ObjectTable = forwardRef(
}))
useEffect(() => {
if (token != null && !pages.includes(initialPage) && !initialized) {
if (
connected == true &&
token != null &&
!pages.includes(initialPage) &&
!initialized
) {
loadInitialPage()
setInitialized(true)
}
}, [token, loadInitialPage, initialPage, pages, initialized])
}, [token, loadInitialPage, initialPage, pages, initialized, connected])
// Watch for changes in type and masterFilter, reset component state when they change
useEffect(() => {

View File

@ -1,22 +1,21 @@
import { useContext, useState, useEffect } from 'react'
import { Typography, Spin, Flex, Space, Slider, Descriptions, Tag } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { PrintServerContext } from '../context/PrintServerContext'
import { ApiServerContext } from '../context/ApiServerContext'
import PropTypes from 'prop-types'
import merge from 'lodash/merge'
const { Text } = Typography
const PrinterMiscPanel = ({
printerId,
showControls = true,
shouldUnsubscribe = true
}) => {
const PrinterMiscPanel = ({ id, showControls = true }) => {
const { subscribeToObjectEvent, connected, sendObjectAction } =
useContext(ApiServerContext)
const [miscData, setMiscData] = useState({
fan: {
speed: 0,
target: 0
},
ledBacklight: {
lcdBacklight: {
// eslint-disable-line camelcase
brightness: 0
},
@ -27,112 +26,81 @@ const PrinterMiscPanel = ({
enabled: false,
filamentDetected: false
},
heaterFan: {
coolingFan: {
speed: 0
}
})
const [initialized, setInitialized] = useState(false)
const { printServer } = useContext(PrintServerContext)
const [fanSpeed, setFanSpeed] = useState(0)
const [ledBrightness, setLedBrightness] = useState(0)
const [lcdBrightness, setLcdBrightness] = useState(0)
const [beeperValue, setBeeperValue] = useState(0)
useEffect(() => {
const params = {
printerId,
objects: {
fan: null,
'filament_switch_sensor fsensor': null,
'output_pin BEEPER_pin': null,
'output_pin LCD_backlight_pin': null,
'heater_fan nozzle_cooling_fan': null
}
}
const notifyMiscStatusUpdate = (statusUpdate) => {
if (statusUpdate?.fan) {
setMiscData((prevData) => ({
...prevData,
fan: statusUpdate.fan
}))
setFanSpeed(statusUpdate.fan.speed)
}
if (statusUpdate?.['heater_fan nozzle_cooling_fan']) {
setMiscData((prevData) => ({
...prevData,
heaterFan: statusUpdate['heater_fan nozzle_cooling_fan']
}))
}
if (statusUpdate?.['output_pin LCD_backlight_pin']) {
setMiscData((prevData) => ({
...prevData,
ledBacklight: statusUpdate['output_pin LCD_backlight_pin'].value
}))
setLedBrightness(statusUpdate['output_pin LCD_backlight_pin'].value)
}
if (statusUpdate?.['output_pin BEEPER_pin']) {
setMiscData((prevData) => ({
...prevData,
beeper: statusUpdate['output_pin BEEPER_pin']
}))
setBeeperValue(statusUpdate['output_pin BEEPER_pin'].value)
}
if (statusUpdate?.['filament_switch_sensor fsensor']) {
setMiscData((prevData) => ({
...prevData,
filamentSensor: {
enabled: statusUpdate['filament_switch_sensor fsensor'].enabled,
filamentDetected:
statusUpdate['filament_switch_sensor fsensor'].filament_detected
}
}))
}
}
if (!initialized && printServer.connected) {
setInitialized(true)
printServer.on('connect', () => {
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
if (id && connected == true) {
console.log('subscribing to misc event')
const miscEventUnsubscribe = subscribeToObjectEvent(
id,
'printer',
'misc',
(event) => {
console.log('misc event', event)
setMiscData((prev) => {
const merged = merge({}, prev, event.data)
return merged
})
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
printServer.on('notify_status_update', notifyMiscStatusUpdate)
}
)
return () => {
if (printServer.connected && initialized && shouldUnsubscribe) {
printServer.off('notify_status_update', notifyMiscStatusUpdate)
printServer.emit('printer.objects.unsubscribe', params)
if (miscEventUnsubscribe) miscEventUnsubscribe()
}
}
}, [printServer, initialized, printerId, shouldUnsubscribe])
}, [id, connected, subscribeToObjectEvent])
useEffect(() => {
if (miscData.fan?.speed !== undefined) {
setFanSpeed(miscData.fan.speed)
}
if (miscData.lcdBacklight?.brightness !== undefined) {
setLcdBrightness(miscData.lcdBacklight.brightness)
}
if (miscData.beeper?.value !== undefined) {
setBeeperValue(miscData.beeper.value)
}
}, [
miscData.fan?.speed,
miscData.lcdBacklight?.brightness,
miscData.beeper?.value
])
const handleSetFanSpeed = (value) => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'gcodeScript',
data: {
script: `M106 S${Math.round(value * 255)}`
}
})
}
}
const handleSetLedBrightness = (value) => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
script: `SET_LED LED=led_backlight BRIGHTNESS=${value}`
const handleSetLcdBrightness = (value) => {
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'gcodeScript',
data: {
script: `SET_LCD LCD=lcd_backlight BRIGHTNESS=${value}`
}
})
}
}
const handleSetBeeperValue = (value) => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'gcodeScript',
data: {
script: `M300 S440 P200 V${Math.round(value * 100)}`
}
})
}
}
@ -156,14 +124,14 @@ const PrinterMiscPanel = ({
tooltip={{ open: false }}
/>
<Text>LED Backlight: {Math.round(ledBrightness * 100)}%</Text>
<Text>LCD Backlight: {Math.round(lcdBrightness * 100)}%</Text>
<Slider
value={ledBrightness}
value={lcdBrightness}
min={0}
max={1}
step={0.01}
onChange={setLedBrightness}
onAfterChange={handleSetLedBrightness}
onChange={setLcdBrightness}
onAfterChange={handleSetLcdBrightness}
tooltip={{ open: false }}
/>
@ -201,9 +169,9 @@ const PrinterMiscPanel = ({
)}
</Descriptions.Item>
<Descriptions.Item label='Nozzle Cooling Fan'>
{miscData.heaterFan.speed > 0 ? (
{miscData.coolingFan.speed > 0 ? (
<Text>
Running ({Math.round(miscData.heaterFan.speed * 100)}%)
Running ({Math.round(miscData.coolingFan.speed * 100)}%)
</Text>
) : (
<Text>Off</Text>
@ -222,9 +190,8 @@ const PrinterMiscPanel = ({
}
PrinterMiscPanel.propTypes = {
printerId: PropTypes.string.isRequired,
showControls: PropTypes.bool,
shouldUnsubscribe: PropTypes.bool
id: PropTypes.string.isRequired,
showControls: PropTypes.bool
}
export default PrinterMiscPanel

View File

@ -10,7 +10,6 @@ import {
Card,
message // eslint-disable-line
} from 'antd'
import { PrintServerContext } from '../context/PrintServerContext'
import PropTypes from 'prop-types'
import LevelBedIcon from '../../Icons/LevelBedIcon'
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon'
@ -18,14 +17,12 @@ import ArrowRightIcon from '../../Icons/ArrowRightIcon'
import ArrowUpIcon from '../../Icons/ArrowUpIcon'
import ArrowDownIcon from '../../Icons/ArrowDownIcon'
import HomeIcon from '../../Icons/HomeIcon'
import { ApiServerContext } from '../context/ApiServerContext'
const PrinterMovementPanel = ({ printerId }) => {
const PrinterMovementPanel = ({ id }) => {
const { connected, sendObjectAction } = useContext(ApiServerContext)
const [posValue, setPosValue] = useState(10)
const [rateValue, setRateValue] = useState(1000)
const { printServer } = useContext(PrintServerContext)
//const messageApi = message.useMessage()
const handlePosRadioChange = (e) => {
const value = e.target.value
setPosValue(value) // Update posValue state when radio button changes
@ -40,20 +37,26 @@ const PrinterMovementPanel = ({ printerId }) => {
}
const handleHomeAxisClick = (axis) => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
script: `G28 ${axis}`
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'homeAxis',
data: {
axis: axis
}
})
}
}
const handleMoveAxisClick = (axis, minus) => {
const distanceValue = !minus ? posValue * -1 : posValue
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
script: `_CLIENT_LINEAR_MOVE ${axis}=${distanceValue} F=${rateValue}`
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'moveAxis',
data: {
axis: axis,
distance: distanceValue,
rate: rateValue
}
})
}
//sendCommand('moveAxis', { axis, pos, rate })
@ -93,7 +96,7 @@ const PrinterMovementPanel = ({ printerId }) => {
return (
<div style={{ minWidth: 190 }}>
<Flex vertical gap={'large'}>
<Flex vertical gap={'large'} align='center'>
<Flex horizontal='true' gap='small'>
<Card size='small' title='XY'>
<Flex
@ -211,23 +214,21 @@ const PrinterMovementPanel = ({ printerId }) => {
min={0.1}
max={100}
value={posValue}
formatter={(value) => `${value} mm`}
parser={(value) => value?.replace(' mm', '')}
suffix='mm'
onChange={handlePosInputChange}
placeholder='10 mm'
name='posInput'
style={{ flexGrow: 1 }}
style={{ flexGrow: 1, minWidth: '125px' }}
/>
<InputNumber
min={1}
max={5000}
value={rateValue}
formatter={(value) => `${value} mm/s`}
parser={(value) => value?.replace(' mm/s', '')}
suffix='mm/s'
onChange={handleRateInputChange}
placeholder='100 mm/s'
name='rateInput'
style={{ flexGrow: 1 }}
style={{ flexGrow: 1, minWidth: '125px' }}
/>
</Flex>
</Flex>
@ -237,7 +238,7 @@ const PrinterMovementPanel = ({ printerId }) => {
}
PrinterMovementPanel.propTypes = {
printerId: PropTypes.string.isRequired
id: PropTypes.string.isRequired
}
export default PrinterMovementPanel

View File

@ -7,14 +7,16 @@ import {
Collapse,
InputNumber,
Descriptions,
Button
Button,
Divider
} from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import { PrintServerContext } from '../context/PrintServerContext'
import { LoadingOutlined, CaretRightOutlined } from '@ant-design/icons'
import { ApiServerContext } from '../context/ApiServerContext'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import BoolDisplay from './BoolDisplay'
import merge from 'lodash/merge'
import { round } from '../utils/Utils'
const { Text } = Typography
const CustomCollapse = styled(Collapse)`
@ -29,91 +31,151 @@ const CustomCollapse = styled(Collapse)`
`
const PrinterPositionPanel = ({
printerId,
id,
showControls = true,
showMoreInfo = true,
shouldUnsubscribe = true
showMoreInfo = true
}) => {
const { subscribeToObjectEvent, connected, sendObjectAction } =
useContext(ApiServerContext)
const [positionData, setPositionData] = useState({
speed_factor: 1.0, // eslint-disable-line
speedFactor: 1.0, // eslint-disable-line
speed: 100,
extrude_factor: 1.0, // eslint-disable-line
absolute_foordinates: true, // eslint-disable-line
absolute_extrude: false, // eslint-disable-line
homing_origin: [0.0, 0.0, 0.0, 0.0], // eslint-disable-line
position: [0.0, 0.0, 0.0, 0.0],
gcode_position: [0.0, 0.0, 0.0, 0.0] // eslint-disable-line
extrudeFactor: 1.0, // eslint-disable-line
absoluteCoordinates: true, // eslint-disable-line
absoluteExtrude: false, // eslint-disable-line
homingOrigin: [0.0, 0.0, 0.0, 0.0], // eslint-disable-line
toolheadPosition: [0.0, 0.0, 0.0, 0.0],
gcodePosition: [0.0, 0.0, 0.0, 0.0], // eslint-disable-line
livePosition: [0.0, 0.0, 0.0, 0.0],
maxVelocity: 1000,
maxAcceleration: 1000,
squareCornerVelocity: 100,
minCruiseRatio: 0
})
const [initialized, setInitialized] = useState(false)
const { printServer } = useContext(PrintServerContext)
const [speedFactor, setSpeedFactor] = useState(positionData.speed_factor)
const [extrudeFactor, setExtrudeFactor] = useState(
positionData.extrude_factor
useEffect(() => {
if (id && connected == true) {
console.log('subscribing to motion event')
const motionEventUnsubscribe = subscribeToObjectEvent(
id,
'printer',
'motion',
(event) => {
console.log('motion event', event)
setPositionData((prev) => {
const merged = merge({}, prev, event.data)
return merged
})
}
)
return () => {
if (motionEventUnsubscribe) motionEventUnsubscribe()
}
}
}, [id, connected])
const [speedFactor, setSpeedFactor] = useState(positionData.speedFactor)
const [extrudeFactor, setExtrudeFactor] = useState(positionData.extrudeFactor)
const [maxVelocity, setMaxVelocity] = useState(positionData.maxVelocity)
const [maxAcceleration, setMaxAcceleration] = useState(
positionData.maxAcceleration
)
const [squareCornerVelocity, setSquareCornerVelocity] = useState(
positionData.squareCornerVelocity
)
const [minCruiseRatio, setMinCruiseRatio] = useState(
positionData.minCruiseRatio
)
useEffect(() => {
const params = {
printerId,
objects: {
toolhead: null,
gcode_move: null // eslint-disable-line
if (positionData.speedFactor !== undefined) {
setSpeedFactor(positionData.speedFactor)
}
if (positionData.extrudeFactor !== undefined) {
setExtrudeFactor(positionData.extrudeFactor)
}
const notifyPositionStatusUpdate = (statusUpdate) => {
if (statusUpdate?.toolhead) {
setPositionData((prevData) => ({
...prevData,
...statusUpdate.toolhead
}))
if (positionData.maxVelocity !== undefined) {
setMaxVelocity(positionData.maxVelocity)
}
if (statusUpdate?.gcode_move) {
setPositionData((prevData) => ({
...prevData,
...statusUpdate.gcode_move
}))
if (positionData.maxAcceleration !== undefined) {
setMaxAcceleration(positionData.maxAcceleration)
}
if (positionData.squareCornerVelocity !== undefined) {
setSquareCornerVelocity(positionData.squareCornerVelocity)
}
if (!initialized && printServer?.connected) {
setInitialized(true)
printServer.on('connect', () => {
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
})
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
printServer.on('notify_status_update', notifyPositionStatusUpdate)
if (positionData.minCruiseRatio !== undefined) {
setMinCruiseRatio(positionData.minCruiseRatio)
}
setSpeedFactor(positionData.speed_factor)
setExtrudeFactor(positionData.extrude_factor)
return () => {
if (printServer?.connected && initialized && shouldUnsubscribe) {
printServer.off('notify_status_update', notifyPositionStatusUpdate)
printServer.emit('printer.objects.unsubscribe', params)
}
}
}, [printServer, initialized, printerId, shouldUnsubscribe])
}, [
positionData.speedFactor,
positionData.extrudeFactor,
positionData.maxVelocity,
positionData.maxAcceleration,
positionData.squareCornerVelocity,
positionData.minCruiseRatio
])
const handleSetSpeedFactor = () => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
script: `M220 S${speedFactor * 100}`
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'setSpeedFactor',
data: {
speedFactor: speedFactor * 100
}
})
}
}
const handleSetExtrudeFactor = () => {
if (printServer) {
printServer.emit('printer.gcode.script', {
printerId,
script: `M221 S${extrudeFactor * 100}`
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'setExtrudeFactor',
data: {
extrudeFactor: extrudeFactor * 100
}
})
}
}
const handleSetMaxVelocity = () => {
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'setMaxVelocity',
data: {
maxVelocity: maxVelocity
}
})
}
}
const handleSetMaxAcceleration = () => {
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'setMaxAcceleration',
data: {
maxAcceleration: maxAcceleration
}
})
}
}
const handleSetSquareCornerVelocity = () => {
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'setSquareCornerVelocity',
data: {
squareCornerVelocity: squareCornerVelocity
}
})
}
}
const handleSetMinCruiseRatio = () => {
if (id && connected == true) {
sendObjectAction(id, 'printer', {
type: 'setMinCruiseRatio',
data: {
minCruiseRatio: minCruiseRatio
}
})
}
}
@ -124,40 +186,156 @@ const PrinterPositionPanel = ({
label: 'More Position Data',
children: (
<>
<Divider style={{ margin: '0 0 12px 0' }} />
<Flex vertical gap={'middle'}>
<Text>GCode Position:</Text>
<Descriptions column={1} size='small' bordered>
<Descriptions
column={1}
size='small'
bordered
styles={{ label: { width: '40px' } }}
>
<Descriptions.Item label='X'>
{positionData.gcode_position[0].toFixed(2)}mm
{' '}
{round(positionData.gcodePosition[0], 2)}mm
</Descriptions.Item>
<Descriptions.Item label='Y'>
{positionData.gcode_position[1].toFixed(2)}mm
{round(positionData.gcodePosition[1], 2)}mm
</Descriptions.Item>
<Descriptions.Item label='Z'>
{positionData.gcode_position[2].toFixed(2)}mm
{round(positionData.gcodePosition[2], 2)}mm
</Descriptions.Item>
<Descriptions.Item label='E'>
{positionData.gcode_position[3].toFixed(2)}mm
{round(positionData.gcodePosition[3], 2)}mm
</Descriptions.Item>
</Descriptions>
{showControls && (
<>
<Space direction='vertical' style={{ width: '100%' }}>
<Space direction='horizontal'>
<Text>Max Velocity:</Text>
<Space.Compact block size='small'>
<InputNumber
value={round(maxVelocity, 2)}
min={1}
max={10000}
step={5}
style={{ width: '125px' }}
suffix='mm/s'
onChange={(value) => setMaxVelocity(value)}
onPressEnter={handleSetMaxVelocity}
size='small'
/>
<Button
type='default'
style={{ width: 40 }}
onClick={handleSetMaxVelocity}
>
Set
</Button>
</Space.Compact>
</Space>
<Space direction='vertical' style={{ width: '100%' }}>
<Space direction='horizontal'>
<Text>Max Acceleration:</Text>
<Space.Compact block size='small'>
<InputNumber
value={round(maxAcceleration, 2)}
min={1}
max={10000}
step={5}
style={{ width: '125px' }}
suffix='mm/s²'
onChange={(value) => setMaxAcceleration(value)}
onPressEnter={handleSetMaxAcceleration}
size='small'
/>
<Button
type='default'
style={{ width: 40 }}
onClick={handleSetMaxAcceleration}
>
Set
</Button>
</Space.Compact>
</Space>
</Space>
<Space direction='vertical' style={{ width: '100%' }}>
<Space direction='horizontal'>
<Text>Sqr Corner Vel:</Text>
<Space.Compact block size='small'>
<InputNumber
value={round(squareCornerVelocity, 2) || 0}
min={0.1}
max={1000}
step={0.1}
suffix='mm/s'
style={{ width: '125px' }}
onChange={(value) => setSquareCornerVelocity(value)}
onPressEnter={handleSetSquareCornerVelocity}
size='small'
/>
<Button
type='default'
style={{ width: 40 }}
onClick={handleSetSquareCornerVelocity}
>
Set
</Button>
</Space.Compact>
</Space>
</Space>
<Space direction='vertical' style={{ width: '100%' }}>
<Space direction='horizontal'>
<Text>Min Cruise Ratio:</Text>
<Space.Compact block size='small'>
<InputNumber
value={round(minCruiseRatio * 100, 2) || 0}
min={0}
max={100}
step={1}
suffix='%'
style={{ width: '125px' }}
onChange={(value) => setMinCruiseRatio(value / 100)}
onPressEnter={handleSetMinCruiseRatio}
size='small'
/>
<Button
type='default'
style={{ width: 40 }}
onClick={handleSetMinCruiseRatio}
>
Set
</Button>
</Space.Compact>
</Space>
</Space>
</Space>
</>
)}
<Text>Homing Origin:</Text>
<Descriptions
column={1}
size='small'
bordered
style={{ flexGrow: 1 }}
styles={{ label: { width: '40px' } }}
>
<Descriptions.Item label='X' span={1}>
{positionData.homing_origin[0].toFixed(2)}mm
{round(positionData.homingOrigin[0], 2)}mm
</Descriptions.Item>
<Descriptions.Item label='Y' span={1}>
{positionData.homing_origin[1].toFixed(2)}mm
{round(positionData.homingOrigin[1], 2)}mm
</Descriptions.Item>
<Descriptions.Item label='Z' span={1}>
{positionData.homing_origin[2].toFixed(2)}mm
{round(positionData.homingOrigin[2], 2)}mm
</Descriptions.Item>
<Descriptions.Item label='E'>
{positionData.homing_origin[3].toFixed(2)}mm
{round(positionData.homingOrigin[3], 2)}mm
</Descriptions.Item>
</Descriptions>
</Flex>
@ -171,18 +349,43 @@ const PrinterPositionPanel = ({
{positionData ? (
<Flex vertical gap='middle'>
<Flex vertical gap={'middle'}>
<Descriptions column={1} size='small' bordered>
<Descriptions
column={1}
size='small'
bordered
styles={{ label: { width: '40px' } }}
>
<Descriptions.Item label='X'>
{positionData.position[0].toFixed(2)}mm
<Space>
<Text>{round(positionData.livePosition[0], 2)}mm</Text>
<Text type='secondary'>
{round(positionData.toolheadPosition[0], 2)}mm
</Text>
</Space>
</Descriptions.Item>
<Descriptions.Item label='Y'>
{positionData.position[1].toFixed(2)}mm
<Space>
<Text>{round(positionData.livePosition[1], 2)}mm</Text>
<Text type='secondary'>
{round(positionData.toolheadPosition[1], 2)}mm
</Text>
</Space>
</Descriptions.Item>
<Descriptions.Item label='Z'>
{positionData.position[2].toFixed(2)}mm
<Space>
<Text>{round(positionData.livePosition[2], 2)}mm</Text>
<Text type='secondary'>
{round(positionData.toolheadPosition[2], 2)}mm
</Text>
</Space>
</Descriptions.Item>
<Descriptions.Item label='E'>
{positionData.position[3].toFixed(2)}mm
<Space>
<Text>{round(positionData.livePosition[3], 2)}mm</Text>
<Text type='secondary'>
{round(positionData.toolheadPosition[3], 2)}mm
</Text>
</Space>
</Descriptions.Item>
</Descriptions>
{showControls && (
@ -192,11 +395,12 @@ const PrinterPositionPanel = ({
<Text>Speed Factor:</Text>
<Space.Compact block size='small'>
<InputNumber
value={speedFactor.toFixed(2)}
value={round(speedFactor, 2)}
min={0.1}
max={2}
step={0.1}
style={{ width: '100px' }}
style={{ width: '125px' }}
suffix='%'
onChange={(value) => setSpeedFactor(value)}
onPressEnter={handleSetSpeedFactor}
size='small'
@ -215,11 +419,12 @@ const PrinterPositionPanel = ({
<Text>Extrude Factor:</Text>
<Space.Compact block size='small'>
<InputNumber
value={extrudeFactor.toFixed(2)}
value={round(extrudeFactor, 2)}
min={0.1}
max={2}
step={0.1}
style={{ width: '100px' }}
style={{ width: '125px' }}
suffix='%'
onChange={(value) => setExtrudeFactor(value)}
onPressEnter={handleSetExtrudeFactor}
size='small'
@ -236,14 +441,29 @@ const PrinterPositionPanel = ({
</Space>
</>
)}
<Descriptions column={1} size='small' bordered>
<Descriptions.Item label='Current Speed'>
{positionData.speed.toFixed(2)}mm/s
<Descriptions
column={1}
size='small'
bordered
styles={{ label: { width: '125px' } }}
>
<Descriptions.Item label='Speed'>
{round(positionData.speed, 2)}mm/s
</Descriptions.Item>
<Descriptions.Item label='Absolute Coordinates'>
<Descriptions.Item label='Abs Coor'>
{positionData ? (
<BoolDisplay
value={positionData.absolute_coordinates}
value={positionData.absoluteCoordinates}
yesNo={true}
/>
) : (
<Text>n/a</Text>
)}
</Descriptions.Item>
<Descriptions.Item label='Abs Extrude'>
{positionData ? (
<BoolDisplay
value={positionData.absoluteExtrude}
yesNo={true}
/>
) : (
@ -259,7 +479,7 @@ const PrinterPositionPanel = ({
size='small'
items={moreInfoItems}
expandIcon={({ isActive }) => (
<CaretLeftOutlined rotate={isActive ? 90 : 0} />
<CaretRightOutlined rotate={isActive ? 90 : 0} />
)}
/>
)}
@ -274,10 +494,9 @@ const PrinterPositionPanel = ({
}
PrinterPositionPanel.propTypes = {
printerId: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
showControls: PropTypes.bool,
showMoreInfo: PropTypes.bool,
shouldUnsubscribe: PropTypes.bool
showMoreInfo: PropTypes.bool
}
export default PrinterPositionPanel

View File

@ -8,7 +8,8 @@ import {
Space,
Collapse,
InputNumber,
Button
Button,
Divider
} from 'antd'
import { LoadingOutlined, CaretRightOutlined } from '@ant-design/icons'
import styled from 'styled-components'
@ -113,6 +114,7 @@ const PrinterTemperaturePanel = ({
label: 'More Temperature Data',
children: (
<>
<Divider style={{ margin: '0 0 12px 0' }} />
<Flex vertical gap={0}>
<Text>
Hot End Power:{' '}

View File

@ -49,7 +49,7 @@ const StateDisplay = ({
strokeColor={
orangeProgressTypes.includes(currentState.type) ? 'orange' : ''
}
style={{ width: '150px', marginBottom: '2px' }}
style={{ maxWidth: '200px', marginBottom: '2px' }}
/>
) : null}

View File

@ -307,8 +307,11 @@ const AuthProvider = ({ children }) => {
setExpiresAt(null)
setUserProfile(null)
clearAuthCookies()
setAuthenticated(false)
if (showSessionExpiredModal == false) {
setShowUnauthorizedModal(true)
}
}
const refreshToken = useCallback(async () => {
try {
@ -489,7 +492,6 @@ const AuthProvider = ({ children }) => {
authCode == null
) {
setInitialized(true)
console.log('Showing unauth', token, authCode)
setShowUnauthorizedModal(true)
setAuthenticated(false)
}

View File

@ -384,7 +384,7 @@ const SpotlightProvider = ({ children }) => {
const getModeDescription = (mode) => {
switch (mode) {
case ':':
return 'ID lookup'
return 'ID/Reference lookup'
case '?':
return 'Filter'
case '^':

View File

@ -30,5 +30,9 @@ export function timeStringToMinutes(timeString) {
return Math.floor(totalMinutes)
}
export function round(num, decimals) {
return Math.round(num * 10 ** decimals) / 10 ** decimals
}
// Re-export the functions for backward compatibility
export {}

View File

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

View File

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

View File

@ -26,6 +26,8 @@ import { DocumentSize } from './models/DocumentSize.js'
import { DocumentTemplate } from './models/DocumentTemplate.js'
import { DocumentPrinter } from './models/DocumentPrinter.js'
import { DocumentJob } from './models/DocumentJob.js'
import { TaxRate } from './models/TaxRate.js'
import { TaxRecord } from './models/TaxRecord.js'
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
export const objectModels = [
@ -56,7 +58,9 @@ export const objectModels = [
DocumentSize,
DocumentTemplate,
DocumentPrinter,
DocumentJob
DocumentJob,
TaxRate,
TaxRecord
]
// Re-export individual models for direct access
@ -88,7 +92,9 @@ export {
DocumentSize,
DocumentTemplate,
DocumentPrinter,
DocumentJob
DocumentJob,
TaxRate,
TaxRecord
}
export function getModelByName(name, ignoreCase = false) {

View File

@ -41,8 +41,9 @@ export const Job = {
'_id',
'gcodeFile',
'gcodeFile._id',
'state',
'quantity',
'state',
'createdAt'
],
filters: ['state', '_id', 'gcodeFile._id', 'quantity'],

View File

@ -4,7 +4,6 @@ import PartIcon from '../../components/Icons/PartIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
export const Part = {
name: 'part',
label: 'Part',
@ -132,28 +131,47 @@ export const Part = {
step: 0.01
},
{
name: 'priceMode',
label: 'Price Mode',
name: 'costWithTax',
label: 'Cost with Tax',
columnWidth: 150,
required: true,
type: 'priceMode',
disabled: (objectData) => {
return objectData.globalPricing == true
readOnly: true,
type: 'number',
prefix: '£',
min: 0,
step: 0.01,
value: (objectData) => {
if (objectData?.costTaxRate?.rateType == 'percentage') {
return (
(
objectData?.cost *
(1 + objectData?.costTaxRate?.rate / 100)
).toFixed(2) || undefined
)
} else if (objectData?.costTaxRate?.rateType == 'amount') {
return (
(objectData?.cost + objectData?.costTaxRate?.rate).toFixed(2) ||
undefined
)
} else {
return objectData?.cost || undefined
}
}
},
{
name: 'margin',
label: 'Margin',
name: 'costTaxRate',
label: 'Cost Tax Rate',
required: true,
type: 'number',
disabled: (objectData) => {
return (
objectData.globalPricing == true || objectData.priceMode == 'amount'
)
type: 'object',
objectType: 'taxRate'
},
suffix: '%',
min: 0,
max: 100,
step: 0.01
{
name: 'costTaxRate._id',
label: 'Cost Tax Rate ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'taxRate'
},
{
name: 'price',
@ -163,15 +181,87 @@ export const Part = {
prefix: '£',
min: 0,
step: 0.1,
roundNumber: 2,
readOnly: (objectData) => {
return objectData?.priceMode == 'margin'
},
value: (objectData) => {
if (objectData?.priceMode == 'margin') {
return objectData?.cost * (1 + objectData?.margin / 100) || undefined
if (
objectData?.priceMode == 'margin' &&
objectData?.margin !== undefined &&
objectData?.margin !== null
) {
return (
(objectData?.cost * (1 + objectData?.margin / 100)).toFixed(2) ||
undefined
)
} else {
return objectData?.price || undefined
}
}
},
{
name: 'priceWithTax',
label: 'Price with Tax',
columnWidth: 150,
required: true,
readOnly: true,
type: 'number',
prefix: '£',
min: 0,
step: 0.01,
value: (objectData) => {
if (objectData?.priceTaxRate?.rateType == 'percentage') {
return (
(
objectData?.price *
(1 + objectData?.priceTaxRate?.rate / 100)
).toFixed(2) || undefined
)
} else if (objectData?.priceTaxRate?.rateType == 'amount') {
return (
(objectData?.price + objectData?.priceTaxRate?.rate).toFixed(2) ||
undefined
)
} else {
return objectData?.price
}
}
},
{
name: 'priceMode',
label: 'Price Mode',
required: true,
type: 'priceMode'
},
{
name: 'margin',
label: 'Margin',
required: true,
type: 'number',
disabled: (objectData) => {
return objectData.priceMode == 'amount'
},
suffix: '%',
min: 0,
max: 100,
step: 0.01
},
{
name: 'priceTaxRate',
label: 'Price Tax Rate',
required: true,
type: 'object',
objectType: 'taxRate'
},
{
name: 'priceTaxRate._id',
label: 'Price Tax Rate ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'taxRate'
},
{
name: 'file',
label: 'File',

View File

@ -59,21 +59,6 @@ export const PurchaseOrder = {
showHyperlink: true,
objectType: 'vendor'
},
{
name: 'courierService',
label: 'Courier Service',
required: true,
type: 'object',
objectType: 'courierService'
},
{
name: 'courierService._id',
label: 'Courier Service ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'courierService'
},
{
name: 'items',
label: 'Order Items',
@ -108,29 +93,97 @@ export const PurchaseOrder = {
return objectData?.item?._id
}
},
{
name: 'quantity',
label: 'Quantity',
type: 'number',
required: true
},
{
name: 'price',
label: 'Price',
name: 'itemCost',
label: 'Item Cost',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
value: (objectData) => {
if (objectData?.item) {
return objectData?.item?.cost || undefined
} else {
return undefined
}
}
},
{
name: 'quantity',
label: 'Quantity',
type: 'number',
required: true,
columnWidth: 150
},
{
name: 'totalCost',
label: 'Total Cost',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
value: (objectData) => {
if (objectData?.price == undefined) {
console.log('PurchaseOrder.js', objectData)
return (
(objectData?.item?.cost || 0) * (objectData?.quantity || 0) ||
(objectData?.itemCost || 0) * (objectData?.quantity || 0) ||
undefined
)
}
},
{
name: 'taxRate',
label: 'Tax Rate',
type: 'object',
objectType: 'taxRate',
value: (objectData) => {
if (objectData?.item) {
console.log(objectData?.item)
return objectData?.item?.costTaxRate || undefined
} else {
return objectData?.part?.price || undefined
return undefined
}
}
},
{
name: 'taxRate._id',
label: 'Tax Rate ID',
type: 'id',
showHyperlink: true,
objectType: 'taxRate',
value: (objectData) => {
return objectData?.taxRate?._id || undefined
}
},
{
name: 'totalCostWithTax',
label: 'Total Cost With Tax',
type: 'number',
required: true,
readOnly: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 175,
value: (objectData) => {
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(
(objectData?.totalCost || 0) *
(1 + objectData?.taxRate?.rate / 100)
).toFixed(2) || undefined
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(
(objectData?.totalCost || 0) + objectData?.taxRate?.rate
).toFixed(2) || undefined
)
} else {
return objectData?.totalCost || undefined
}
}
}
@ -149,13 +202,27 @@ export const PurchaseOrder = {
}
},
{
name: 'totalPrice',
name: 'totalCost',
label: 'Total',
type: 'number',
prefix: '£',
property: 'price',
property: 'totalCost',
value: (objectData) => {
return objectData?.items?.reduce((acc, item) => acc + item.price, 0)
return objectData?.items
?.reduce((acc, item) => acc + (item.totalCost || 0), 0)
.toFixed(2)
}
},
{
name: 'totalCostWithTax',
label: 'Total',
type: 'number',
prefix: '£',
property: 'totalCostWithTax',
value: (objectData) => {
return objectData?.items
?.reduce((acc, item) => acc + (item.totalCostWithTax || 0), 0)
.toFixed(2)
}
}
]

View File

@ -68,6 +68,36 @@ export const SubJob = {
readOnly: true,
columnWidth: 175
},
{
name: 'moonrakerJobId',
label: 'Moonraker Job ID',
type: 'miscId',
columnWidth: 140,
showCopy: true
},
{
name: 'startedAt',
label: 'Started At',
type: 'dateTime',
readOnly: true
},
{
name: 'createdPartStock',
label: 'Created Part Stock',
type: 'bool',
readOnly: true
},
{
name: 'finishedAt',
label: 'Finished At',
type: 'dateTime',
readOnly: true
},
{
name: 'job',
label: 'Job',
@ -83,27 +113,6 @@ export const SubJob = {
objectType: 'job'
},
{
name: 'startedAt',
label: 'Started At',
type: 'dateTime',
readOnly: true
},
{
name: 'moonrakerJobId',
label: 'Moonraker Job ID',
type: 'miscId',
columnWidth: 140,
showCopy: true
},
{
name: 'finishedAt',
label: 'Finished At',
type: 'dateTime',
readOnly: true
},
{
name: 'printer',
label: 'Printer',

View File

@ -0,0 +1,181 @@
import TaxRateIcon from '../../components/Icons/TaxRateIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const TaxRate = {
name: 'taxRate',
label: 'Tax Rate',
prefix: 'TXR',
icon: TaxRateIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/taxrates/info?taxRateId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/taxrates/info?taxRateId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
row: true,
icon: EditIcon,
url: (_id) =>
`/dashboard/management/taxrates/info?taxRateId=${_id}&action=edit`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'finishEdit',
label: 'Save Edits',
icon: CheckIcon,
url: (_id) =>
`/dashboard/management/taxrates/info?taxRateId=${_id}&action=finishEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/management/taxrates/info?taxRateId=${_id}&action=cancelEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{ type: 'divider' },
{
name: 'delete',
label: 'Delete',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/management/taxrates/info?taxRateId=${_id}&action=delete`
}
],
columns: [
'name',
'_id',
'rate',
'rateType',
'active',
'country',
'createdAt'
],
filters: ['name', '_id', 'rate', 'rateType', 'active', 'country'],
sorters: [
'name',
'rate',
'rateType',
'active',
'country',
'createdAt',
'_id'
],
group: ['country', 'rateType'],
properties: [
{
name: '_id',
label: 'ID',
columnFixed: 'left',
type: 'id',
objectType: 'taxRate',
showCopy: true
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true
},
{
name: 'name',
label: 'Name',
columnFixed: 'left',
required: true,
type: 'text'
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
},
{
name: 'active',
label: 'Active',
required: true,
type: 'bool',
default: true
},
{
name: 'effectiveFrom',
label: 'Effective From',
type: 'date',
readOnly: false,
required: false
},
{
name: 'rate',
label: 'Rate',
required: true,
type: 'number',
min: 0,
step: 0.01,
prefix: (objectData) => {
return objectData?.rateType == 'amount' ? '£' : undefined
},
suffix: (objectData) => {
return objectData?.rateType == 'percentage' ? '%' : undefined
}
},
{
name: 'effectiveTo',
label: 'Effective To',
type: 'date',
readOnly: false,
required: false
},
{
name: 'rateType',
label: 'Rate Type',
required: true,
type: 'select',
options: [
{ label: 'Percentage', value: 'percentage' },
{ label: 'Amount', value: 'amount' }
]
},
{
name: 'country',
label: 'Country',
type: 'country',
readOnly: false,
required: false
},
{
name: 'description',
label: 'Description',
type: 'text',
readOnly: false,
required: false
}
]
}

View File

@ -0,0 +1,174 @@
import TaxRecordIcon from '../../components/Icons/TaxRecordIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const TaxRecord = {
name: 'taxRecord',
label: 'Tax Record',
prefix: 'TXR',
icon: TaxRecordIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/taxrecords/info?taxRecordId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
row: true,
icon: EditIcon,
url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=edit`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'finishEdit',
label: 'Save Edits',
icon: CheckIcon,
url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=finishEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=cancelEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{ type: 'divider' },
{
name: 'delete',
label: 'Delete',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=delete`
}
],
columns: [
'_id',
'taxRate',
'transactionType',
'transaction',
'amount',
'taxAmount',
'transactionDate',
'createdAt'
],
filters: ['taxRate', 'transactionType', 'transaction', 'transactionDate'],
sorters: ['transactionDate', 'amount', 'taxAmount', 'createdAt', '_id'],
group: ['transactionType'],
properties: [
{
name: '_id',
label: 'ID',
columnFixed: 'left',
type: 'id',
objectType: 'taxRecord',
showCopy: true
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true
},
{
name: 'taxRate',
label: 'Tax Rate',
required: true,
type: 'object',
objectType: 'taxRate'
},
{
name: 'taxRate._id',
label: 'Tax Rate ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'taxRate'
},
{
name: 'transactionType',
label: 'Transaction Type',
required: true,
type: 'select',
options: [
{ label: 'Purchase Order', value: 'purchaseOrder' },
{ label: 'Sales Order', value: 'salesOrder' },
{ label: 'Other', value: 'other' }
]
},
{
name: 'transaction',
label: 'Transaction',
required: true,
type: 'object',
objectType: (objectData) => {
return objectData?.transactionType || 'purchaseOrder'
}
},
{
name: 'transaction._id',
label: 'Transaction ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: (objectData) => {
return objectData?.transactionType || 'purchaseOrder'
}
},
{
name: 'amount',
label: 'Amount',
required: true,
type: 'currency',
min: 0,
step: 0.01
},
{
name: 'taxAmount',
label: 'Tax Amount',
required: true,
type: 'currency',
min: 0,
step: 0.01
},
{
name: 'transactionDate',
label: 'Transaction Date',
required: true,
type: 'date',
default: () => new Date()
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
}
]
}

View File

@ -33,6 +33,10 @@ import DocumentJobInfo from '../components/Dashboard/Management/DocumentJobs/Doc
import DocumentTemplateDesign from '../components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx'
import Files from '../components/Dashboard/Management/Files.jsx'
import FileInfo from '../components/Dashboard/Management/Files/FileInfo.jsx'
import TaxRates from '../components/Dashboard/Management/TaxRates.jsx'
import TaxRateInfo from '../components/Dashboard/Management/TaxRates/TaxRateInfo.jsx'
import TaxRecords from '../components/Dashboard/Management/TaxRecords.jsx'
import TaxRecordInfo from '../components/Dashboard/Management/TaxRecords/TaxRecordInfo.jsx'
const ManagementRoutes = [
<Route key='filaments' path='management/filaments' element={<Filaments />} />,
@ -147,7 +151,23 @@ const ManagementRoutes = [
/>,
<Route key='users' path='management/users' element={<Users />} />,
<Route key='settings' path='management/settings' element={<Settings />} />,
<Route key='auditlogs' path='management/auditlogs' element={<AuditLogs />} />
<Route key='auditlogs' path='management/auditlogs' element={<AuditLogs />} />,
<Route key='taxrates' path='management/taxrates' element={<TaxRates />} />,
<Route
key='taxrates-info'
path='management/taxrates/info'
element={<TaxRateInfo />}
/>,
<Route
key='taxrecords'
path='management/taxrecords'
element={<TaxRecords />}
/>,
<Route
key='taxrecords-info'
path='management/taxrecords/info'
element={<TaxRecordInfo />}
/>
]
export default ManagementRoutes