Added tax rates, tax records and minor style improvements.

This commit is contained in:
Tom Butcher 2025-12-03 23:35:30 +00:00
parent d0911c1166
commit 13bbcbe50e
37 changed files with 1728 additions and 140 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-selection-item .ant-tag,
.ant-select-tree-title .ant-tag { .ant-select-tree-title .object-display-tag {
background: transparent !important; 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 FileIcon from '../../Icons/FileIcon'
import CourierIcon from '../../Icons/CourierIcon' import CourierIcon from '../../Icons/CourierIcon'
import CourierServiceIcon from '../../Icons/CourierServiceIcon' import CourierServiceIcon from '../../Icons/CourierServiceIcon'
import TaxRateIcon from '../../Icons/TaxRateIcon'
import TaxRecordIcon from '../../Icons/TaxRecordIcon'
const items = [ const items = [
{ {
@ -65,6 +67,19 @@ const items = [
path: '/dashboard/management/courierservices' path: '/dashboard/management/courierservices'
}, },
{ type: 'divider' }, { 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', key: 'noteTypes',
icon: <NoteTypeIcon />, icon: <NoteTypeIcon />,
@ -156,6 +171,8 @@ const routeKeyMap = {
'/dashboard/management/vendors': 'vendors', '/dashboard/management/vendors': 'vendors',
'/dashboard/management/couriers': 'couriers', '/dashboard/management/couriers': 'couriers',
'/dashboard/management/courierservices': 'courierServices', '/dashboard/management/courierservices': 'courierServices',
'/dashboard/management/taxrates': 'taxRates',
'/dashboard/management/taxrecords': 'taxRecords',
'/dashboard/management/materials': 'materials', '/dashboard/management/materials': 'materials',
'/dashboard/management/notetypes': 'noteTypes', '/dashboard/management/notetypes': 'noteTypes',
'/dashboard/management/settings': 'settings', '/dashboard/management/settings': 'settings',

View File

@ -25,8 +25,12 @@ const NewPart = ({ onOk }) => {
visibleProperties={{ visibleProperties={{
file: false, file: false,
priceMode: false, priceMode: false,
globalPricing: false, cost: false,
amount: false, costTaxRate: false,
costWithTax: false,
price: false,
priceTaxRate: false,
priceWithTax: false,
margin: false margin: false
}} }}
/> />
@ -45,9 +49,13 @@ const NewPart = ({ onOk }) => {
objectData={objectData} objectData={objectData}
visibleProperties={{ visibleProperties={{
priceMode: true, priceMode: true,
globalPricing: true, margin: true,
amount: true, cost: true,
margin: 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={{ visibleProperties={{
parts: false parts: false
}} }}
labelWidth='200px'
/> />
</InfoCollapse> </InfoCollapse>
<InfoCollapse <InfoCollapse

View File

@ -171,22 +171,42 @@ const ControlPrinter = () => {
const sideBarItems = ( const sideBarItems = (
<Flex gap={isMobile ? 'large' : 'middle'} vertical> <Flex gap={isMobile ? 'large' : 'middle'} vertical>
{collapseState.temperature && ( {collapseState.temperature && (
<Card style={{ width: '100%' }} title='Temperature' size='small'> <Card
style={{ width: '100%' }}
title='Temperature'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterTemperaturePanel id={printerId} /> <PrinterTemperaturePanel id={printerId} />
</Card> </Card>
)} )}
{collapseState.position && ( {collapseState.position && (
<Card style={{ width: '100%' }} title='Position' size='small'> <Card
style={{ width: '100%' }}
title='Position'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterPositionPanel id={printerId} /> <PrinterPositionPanel id={printerId} />
</Card> </Card>
)} )}
{collapseState.movement && ( {collapseState.movement && (
<Card style={{ width: '100%' }} title='Movement' size='small'> <Card
style={{ width: '100%' }}
title='Movement'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterMovementPanel id={printerId} /> <PrinterMovementPanel id={printerId} />
</Card> </Card>
)} )}
{collapseState.misc && ( {collapseState.misc && (
<Card style={{ width: '100%' }} title='Misc' size='small'> <Card
style={{ width: '100%' }}
title='Misc'
size='small'
styles={{ body: { padding: '25px' } }}
>
<PrinterMiscPanel id={printerId} /> <PrinterMiscPanel id={printerId} />
</Card> </Card>
)} )}
@ -342,6 +362,7 @@ const ControlPrinter = () => {
}} }}
objectData={printerObjectData} objectData={printerObjectData}
type='printer' type='printer'
labelWidth='100px'
/> />
) )
}} }}
@ -373,7 +394,8 @@ const ControlPrinter = () => {
showHyperlink={true} showHyperlink={true}
visibleProperties={{ visibleProperties={{
printers: false, printers: false,
createdAt: false createdAt: false,
finishedAt: false
}} }}
objectData={jobObjectData} objectData={jobObjectData}
type='job' type='job'
@ -414,7 +436,8 @@ const ControlPrinter = () => {
showHyperlink={true} showHyperlink={true}
visibleProperties={{ visibleProperties={{
printers: false, printers: false,
createdAt: false createdAt: false,
finishedAt: false
}} }}
objectData={subJobObjectData} objectData={subJobObjectData}
type='subJob' type='subJob'

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import { Table, Skeleton, Card, Button, Flex, Form, Typography } from 'antd'
import PlusIcon from '../../Icons/PlusIcon' import PlusIcon from '../../Icons/PlusIcon'
import ObjectProperty from './ObjectProperty' import ObjectProperty from './ObjectProperty'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import BinIcon from '../../Icons/BinIcon'
const { Text } = Typography const { Text } = Typography
const DEFAULT_COLUMN_WIDTHS = { const DEFAULT_COLUMN_WIDTHS = {
@ -135,8 +136,70 @@ const ObjectChildTable = ({
} }
})) }))
return [...propertyColumns, ...additionalColumns] const deleteColumn = isEditing
}, [resolvedProperties, additionalColumns, 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(() => { const skeletonData = useMemo(() => {
return createSkeletonRows( return createSkeletonRows(
@ -213,7 +276,7 @@ const ObjectChildTable = ({
}, [properties, rollups, objectData]) }, [properties, rollups, objectData])
const rollupColumns = useMemo(() => { const rollupColumns = useMemo(() => {
return properties.map((property, index) => { const propertyColumns = properties.map((property, index) => {
const nextProperty = properties[index + 1] const nextProperty = properties[index + 1]
var nextRollup = null var nextRollup = null
if (nextProperty) { if (nextProperty) {
@ -231,9 +294,14 @@ const ObjectChildTable = ({
return ( return (
<Flex justify={'space-between'}> <Flex justify={'space-between'}>
<Text> <Text>
{record[property.name] !== undefined &&
record[property.name] !== null && (
<>
{property?.prefix} {property?.prefix}
{record[property.name]} {record[property.name]}
{property?.suffix} {property?.suffix}
</>
)}
</Text> </Text>
{rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>} {rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
</Flex> </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( const hasRollups = useMemo(
() => Array.isArray(rollups) && rollups.length > 0, () => Array.isArray(rollups) && rollups.length > 0,
@ -339,7 +422,7 @@ const ObjectChildTable = ({
if (isEditing === true && formListName) { if (isEditing === true && formListName) {
return ( return (
<Form.List name={formListName}> <Form.List name={formListName}>
{(fields, { add }) => { {(fields, { add, remove }) => {
const listDataSource = fields.map((field, index) => ({ const listDataSource = fields.map((field, index) => ({
_field: field, _field: field,
_index: index, _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 = ( const listTable = (
<Flex vertical> <Flex vertical>
<div ref={mainTableWrapperRef}> <div ref={mainTableWrapperRef}>
<Table <Table
dataSource={listDataSource} dataSource={listDataSource}
columns={[...listColumns, ...additionalColumns]} columns={[...listColumns, ...additionalColumns, deleteColumn]}
pagination={false} pagination={false}
size={size} size={size}
loading={loading} loading={loading}

View File

@ -53,13 +53,30 @@ const ObjectDisplay = ({ object, objectType }) => {
const model = getModelByName(objectType) const model = getModelByName(objectType)
const Icon = model.icon const Icon = model.icon
return ( return (
<Tag style={{ margin: 0, border: 'none' }}> <Tag
<Flex gap={objectData?.color ? 'small' : '3px'} align='center'> style={{
<Icon /> margin: 0,
<Flex gap={'small'} align='center'> 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} {objectData?.color ? <Badge color={objectData?.color} /> : null}
<div style={{ paddingTop: '1.5px' }}> <div style={{ paddingTop: '1.5px', minWidth: 0 }}>
{objectData?.name ? <Text ellipsis>{objectData.name}</Text> : null} {objectData?.name ? (
<Text ellipsis style={{ lineHeight: '1' }}>
{objectData.name}
</Text>
) : null}
{objectData?._id && !objectData?.name ? ( {objectData?._id && !objectData?.name ? (
<IdDisplay <IdDisplay
id={objectData?._id} id={objectData?._id}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -189,7 +189,12 @@ const PrinterPositionPanel = ({
<Divider style={{ margin: '0 0 12px 0' }} /> <Divider style={{ margin: '0 0 12px 0' }} />
<Flex vertical gap={'middle'}> <Flex vertical gap={'middle'}>
<Text>GCode Position:</Text> <Text>GCode Position:</Text>
<Descriptions column={1} size='small' bordered> <Descriptions
column={1}
size='small'
bordered
styles={{ label: { width: '40px' } }}
>
<Descriptions.Item label='X'> <Descriptions.Item label='X'>
{' '} {' '}
{round(positionData.gcodePosition[0], 2)}mm {round(positionData.gcodePosition[0], 2)}mm
@ -318,6 +323,7 @@ const PrinterPositionPanel = ({
size='small' size='small'
bordered bordered
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
styles={{ label: { width: '40px' } }}
> >
<Descriptions.Item label='X' span={1}> <Descriptions.Item label='X' span={1}>
{round(positionData.homingOrigin[0], 2)}mm {round(positionData.homingOrigin[0], 2)}mm
@ -343,7 +349,12 @@ const PrinterPositionPanel = ({
{positionData ? ( {positionData ? (
<Flex vertical gap='middle'> <Flex vertical gap='middle'>
<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'> <Descriptions.Item label='X'>
<Space> <Space>
<Text>{round(positionData.livePosition[0], 2)}mm</Text> <Text>{round(positionData.livePosition[0], 2)}mm</Text>
@ -430,7 +441,12 @@ const PrinterPositionPanel = ({
</Space> </Space>
</> </>
)} )}
<Descriptions column={1} size='small' bordered> <Descriptions
column={1}
size='small'
bordered
styles={{ label: { width: '125px' } }}
>
<Descriptions.Item label='Speed'> <Descriptions.Item label='Speed'>
{round(positionData.speed, 2)}mm/s {round(positionData.speed, 2)}mm/s
</Descriptions.Item> </Descriptions.Item>

View File

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

View File

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

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

View File

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

View File

@ -59,21 +59,6 @@ export const PurchaseOrder = {
showHyperlink: true, showHyperlink: true,
objectType: 'vendor' 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', name: 'items',
label: 'Order Items', label: 'Order Items',
@ -108,29 +93,97 @@ export const PurchaseOrder = {
return objectData?.item?._id return objectData?.item?._id
} }
}, },
{ {
name: 'quantity', name: 'itemCost',
label: 'Quantity', label: 'Item Cost',
type: 'number',
required: true
},
{
name: 'price',
label: 'Price',
type: 'number', type: 'number',
required: true, required: true,
prefix: '£', prefix: '£',
min: 0, min: 0,
step: 0.01, 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) => { value: (objectData) => {
if (objectData?.price == undefined) {
console.log('PurchaseOrder.js', objectData)
return ( return (
(objectData?.item?.cost || 0) * (objectData?.quantity || 0) || (objectData?.itemCost || 0) * (objectData?.quantity || 0) ||
undefined 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 { } 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', label: 'Total',
type: 'number', type: 'number',
prefix: '£', prefix: '£',
property: 'price', property: 'totalCost',
value: (objectData) => { 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, readOnly: true,
columnWidth: 175 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', name: 'job',
label: 'Job', label: 'Job',
@ -83,27 +113,6 @@ export const SubJob = {
objectType: 'job' 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', name: 'printer',
label: '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 DocumentTemplateDesign from '../components/Dashboard/Management/DocumentTemplates/DocumentTemplateDesign.jsx'
import Files from '../components/Dashboard/Management/Files.jsx' import Files from '../components/Dashboard/Management/Files.jsx'
import FileInfo from '../components/Dashboard/Management/Files/FileInfo.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 = [ const ManagementRoutes = [
<Route key='filaments' path='management/filaments' element={<Filaments />} />, <Route key='filaments' path='management/filaments' element={<Filaments />} />,
@ -147,7 +151,23 @@ const ManagementRoutes = [
/>, />,
<Route key='users' path='management/users' element={<Users />} />, <Route key='users' path='management/users' element={<Users />} />,
<Route key='settings' path='management/settings' element={<Settings />} />, <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 export default ManagementRoutes