Add finance dashboard components and routes
- Introduced FinanceOverview, FinanceSidebar, and Invoices components for the finance dashboard. - Added InvoiceInfo and NewInvoice components for managing invoices. - Created SVG icons for finance and invoice. - Updated routing to include finance-related paths. - Enhanced DashboardBreadcrumb and DashboardNavigation to support finance navigation. - Defined Invoice model with actions and properties for invoice management.
This commit is contained in:
parent
d7827ecb6d
commit
cabc68c932
1
assets/icons/financeicon.svg
Normal file
1
assets/icons/financeicon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?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><path d="M29.953,40.788l10.342,0c1.761,0 3.18,1.258 3.18,3.055c0,1.788 -1.349,3.03 -3.18,3.03l-18.004,0c-1.785,0 -3.086,-1.234 -3.086,-3.075c0,-1.581 0.953,-2.598 2.336,-3.193l0.024,-0.01c2.054,-0.833 3.153,-2.551 3.153,-4.757c0,-0.425 -0.052,-0.856 -0.133,-1.305l-2.8,0c-1.588,0 -2.701,-1.131 -2.701,-2.591c0,-1.408 1.105,-2.56 2.701,-2.56l1.544,-0c-0.168,-0.87 -0.242,-1.672 -0.242,-2.485c0,-6.288 4.756,-9.887 11.404,-9.887c2.234,0 3.578,0.148 5.274,0.766c1.53,0.469 2.646,1.469 2.646,3.106c0,0.878 -0.321,1.553 -0.828,2.023c-0.48,0.446 -1.159,0.725 -2.011,0.725c-0.479,0 -1.089,-0.115 -1.715,-0.248l-0.045,-0.01c-0.697,-0.176 -1.631,-0.301 -2.775,-0.301c-2.932,0 -5.078,1.33 -5.078,3.98c0,0.734 0.074,1.394 0.272,2.332l7.292,0c1.585,0 2.701,1.163 2.701,2.56c0,1.449 -1.124,2.591 -2.701,2.591l-6.16,0c0.024,0.361 0.034,0.743 0.034,1.147c0,1.832 -0.479,3.671 -1.444,5.108Z"/><path d="M12.892,61l38.215,0c6.616,0 9.892,-3.245 9.892,-9.735l0,-38.499c0,-6.49 -3.276,-9.766 -9.892,-9.766l-38.215,-0c-6.584,0 -9.892,3.276 -9.892,9.766l0,38.499c0,6.49 3.308,9.735 9.892,9.735Zm0.063,-5.072c-3.15,0 -4.883,-1.67 -4.883,-4.946l0,-37.932c0,-3.276 1.733,-4.978 4.883,-4.978l38.089,0c3.119,0 4.883,1.701 4.883,4.978l0,37.932c0,3.276 -1.764,4.946 -4.883,4.946l-38.089,0Z" style="fill-rule:nonzero;"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
14
assets/icons/invoiceicon.svg
Normal file
14
assets/icons/invoiceicon.svg
Normal 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.58577,0,0,0.58577,29,29)">
|
||||||
|
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
|
||||||
|
<g transform="matrix(0.664312,0,0,0.664312,5.121464,12.135807)">
|
||||||
|
<path d="M37.594,52.969C39.062,52.969 40.625,52.281 41.875,51L61.844,31.094C62.938,30 63.656,28.188 63.656,26.5C63.656,24.812 62.938,23 61.844,21.906L41.875,1.969C40.625,0.688 39.062,0 37.594,0C33.812,0 31.406,2.562 31.406,5.906C31.406,7.875 32.312,9.25 33.5,10.406L40.5,17.344L50.219,26.5L40.5,35.656L33.5,42.562C32.312,43.688 31.406,45.094 31.406,47.062C31.406,50.406 33.812,52.969 37.594,52.969ZM1.485,32.781L37.75,32.781L51.969,32.094C55.531,31.938 57.906,29.844 57.906,26.5C57.906,23.156 55.531,21.062 51.969,20.906L37.75,20.219L1.485,20.219C-2.515,20.219 -5.14,22.719 -5.14,26.5C-5.14,30.281 -2.515,32.781 1.485,32.781Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.739137,0,0,0.739137,-0,2.611674)">
|
||||||
|
<path d="M35.853,52.047L25.734,52.047C20.578,52.047 17.797,48.969 17.047,43.844L12.484,12.719L3.906,12.719C1.812,12.719 0,10.906 0,8.766C0,6.625 1.812,4.828 3.906,4.828L14.172,4.828C18.125,4.828 19.609,6.437 20.047,9.625L20.402,12.109L69.062,12.109C71.922,12.109 73.438,13.75 73.438,16.031C73.438,16.5 73.344,17.062 73.281,17.516L71.172,31.781C71.146,31.963 71.118,32.143 71.087,32.319L63.084,32.319C63.272,32.028 63.394,31.657 63.453,31.219L65.078,19.094L21.4,19.094L23.397,33.062L43.157,33.063C41.542,33.617 40.191,34.464 39.103,35.547C37.914,36.73 37.007,38.224 36.457,40.031L24.393,40.031L24.844,43.188C25,44.312 25.656,45.094 26.719,45.094L35.853,45.094L35.853,52.047ZM28.422,68.437C25.078,68.437 22.359,65.766 22.359,62.391C22.359,59.063 25.078,56.359 28.422,56.359C31.781,56.359 34.453,59.063 34.453,62.391C34.453,65.766 31.781,68.437 28.422,68.437Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
61
src/components/Dashboard/Finance/FinanceOverview.jsx
Normal file
61
src/components/Dashboard/Finance/FinanceOverview.jsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { useContext } from 'react'
|
||||||
|
import { Flex } from 'antd'
|
||||||
|
import useCollapseState from '../hooks/useCollapseState'
|
||||||
|
import StatsDisplay from '../common/StatsDisplay'
|
||||||
|
import InfoCollapse from '../common/InfoCollapse'
|
||||||
|
import ScrollBox from '../common/ScrollBox'
|
||||||
|
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
|
|
||||||
|
const FinanceOverview = () => {
|
||||||
|
const { connected } = useContext(ApiServerContext)
|
||||||
|
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
|
'FinanceOverview',
|
||||||
|
{
|
||||||
|
invoiceStats: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!connected) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{
|
||||||
|
maxHeight: '100%',
|
||||||
|
minHeight: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Invoice Statistics'
|
||||||
|
icon={null}
|
||||||
|
active={collapseState.invoiceStats}
|
||||||
|
onToggle={(isActive) =>
|
||||||
|
updateCollapseState('invoiceStats', isActive)
|
||||||
|
}
|
||||||
|
className='no-t-padding-collapse'
|
||||||
|
collapseKey='invoiceStats'
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
justify='flex-start'
|
||||||
|
gap='middle'
|
||||||
|
wrap='wrap'
|
||||||
|
align='flex-start'
|
||||||
|
>
|
||||||
|
<StatsDisplay objectType='invoice' />
|
||||||
|
</Flex>
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FinanceOverview
|
||||||
|
|
||||||
46
src/components/Dashboard/Finance/FinanceSidebar.jsx
Normal file
46
src/components/Dashboard/Finance/FinanceSidebar.jsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import DashboardSidebar from '../common/DashboardSidebar'
|
||||||
|
import InvoiceIcon from '../../Icons/InvoiceIcon'
|
||||||
|
import FinanceIcon from '../../Icons/FinanceIcon'
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
key: 'overview',
|
||||||
|
label: 'Overview',
|
||||||
|
icon: <FinanceIcon />,
|
||||||
|
path: '/dashboard/finance/overview'
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'invoices',
|
||||||
|
label: 'Invoices',
|
||||||
|
icon: <InvoiceIcon />,
|
||||||
|
path: '/dashboard/finance/invoices'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const routeKeyMap = {
|
||||||
|
'/dashboard/finance/overview': 'overview',
|
||||||
|
'/dashboard/finance/invoices': 'invoices'
|
||||||
|
}
|
||||||
|
|
||||||
|
const FinanceSidebar = (props) => {
|
||||||
|
const location = useLocation()
|
||||||
|
const selectedKey = (() => {
|
||||||
|
const match = Object.keys(routeKeyMap).find((path) => {
|
||||||
|
const pathSplit = path.split('/')
|
||||||
|
const locationPathSplit = location.pathname.split('/')
|
||||||
|
if (pathSplit.length > locationPathSplit.length) return false
|
||||||
|
for (let i = 0; i < pathSplit.length; i++) {
|
||||||
|
if (pathSplit[i] !== locationPathSplit[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return match ? routeKeyMap[match] : 'overview'
|
||||||
|
})()
|
||||||
|
|
||||||
|
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FinanceSidebar
|
||||||
|
|
||||||
98
src/components/Dashboard/Finance/Invoices.jsx
Normal file
98
src/components/Dashboard/Finance/Invoices.jsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { useState, useRef } from 'react'
|
||||||
|
import { Button, Flex, Space, Dropdown, Modal } from 'antd'
|
||||||
|
import NewInvoice from './Invoices/NewInvoice'
|
||||||
|
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 Invoices = () => {
|
||||||
|
const [newInvoiceOpen, setNewInvoiceOpen] = useState(false)
|
||||||
|
const tableRef = useRef()
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useViewMode('invoices')
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
useColumnVisibility('invoices')
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Invoice',
|
||||||
|
key: 'newInvoice',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newInvoice') {
|
||||||
|
setNewInvoiceOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='invoice'
|
||||||
|
loading={false}
|
||||||
|
visibleState={columnVisibility}
|
||||||
|
updateVisibleState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='invoice'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newInvoiceOpen}
|
||||||
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
onCancel={() => {
|
||||||
|
setNewInvoiceOpen(false)
|
||||||
|
}}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<NewInvoice
|
||||||
|
onOk={() => {
|
||||||
|
setNewInvoiceOpen(false)
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={newInvoiceOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Invoices
|
||||||
218
src/components/Dashboard/Finance/Invoices/InvoiceInfo.jsx
Normal file
218
src/components/Dashboard/Finance/Invoices/InvoiceInfo.jsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { Space, Flex, Card } from 'antd'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import loglevel from 'loglevel'
|
||||||
|
import config from '../../../../config.js'
|
||||||
|
import useCollapseState from '../../hooks/useCollapseState.js'
|
||||||
|
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||||
|
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||||
|
import ViewButton from '../../common/ViewButton.jsx'
|
||||||
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||||
|
import EditButtons from '../../common/EditButtons.jsx'
|
||||||
|
import 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'
|
||||||
|
import { getModelByName } from '../../../../database/ObjectModels.js'
|
||||||
|
|
||||||
|
const log = loglevel.getLogger('InvoiceInfo')
|
||||||
|
log.setLevel(config.logLevel)
|
||||||
|
|
||||||
|
const InvoiceInfo = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
|
const invoiceId = new URLSearchParams(location.search).get('invoiceId')
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
|
'InvoiceInfo',
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editDisabled = getModelByName('invoice')
|
||||||
|
?.actions?.find((action) => action.name === 'edit')
|
||||||
|
?.disabled(objectFormState.objectData) ?? false
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{
|
||||||
|
maxHeight: '100%',
|
||||||
|
minHeight: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='middle'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='invoice'
|
||||||
|
id={invoiceId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Invoice Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='invoice'
|
||||||
|
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 ||
|
||||||
|
editDisabled
|
||||||
|
}
|
||||||
|
loading={objectFormState.editLoading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={invoiceId}
|
||||||
|
type='invoice'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Invoice Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('info', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
indicator={<LoadingOutlined />}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='invoice'
|
||||||
|
labelWidth='225px'
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Notes'
|
||||||
|
icon={<NoteIcon />}
|
||||||
|
active={collapseState.notes}
|
||||||
|
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||||
|
collapseKey='notes'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel _id={invoiceId} type='invoice' />
|
||||||
|
</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': invoiceId }}
|
||||||
|
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InvoiceInfo
|
||||||
|
|
||||||
113
src/components/Dashboard/Finance/Invoices/NewInvoice.jsx
Normal file
113
src/components/Dashboard/Finance/Invoices/NewInvoice.jsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm
|
||||||
|
type={'invoice'}
|
||||||
|
reset={reset}
|
||||||
|
defaultValues={{
|
||||||
|
state: { type: 'draft' },
|
||||||
|
invoiceType: 'sales',
|
||||||
|
...defaultValues
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='invoice'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
orderType: true,
|
||||||
|
order: true,
|
||||||
|
vendor: true,
|
||||||
|
invoiceDate: true,
|
||||||
|
dueDate: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Optional',
|
||||||
|
key: 'optional',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='invoice'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={false}
|
||||||
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
relatedOrderType: true,
|
||||||
|
relatedOrder: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='invoice'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false,
|
||||||
|
_reference: false,
|
||||||
|
totalAmount: false,
|
||||||
|
totalAmountWithTax: false,
|
||||||
|
totalTaxAmount: false,
|
||||||
|
shippingAmount: false,
|
||||||
|
shippingAmountWithTax: false,
|
||||||
|
grandTotalAmount: false,
|
||||||
|
sentAt: false,
|
||||||
|
paidAt: false,
|
||||||
|
cancelledAt: false,
|
||||||
|
overdueAt: false
|
||||||
|
}}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Invoice'
|
||||||
|
onSubmit={async () => {
|
||||||
|
const result = await handleSubmit()
|
||||||
|
if (result) {
|
||||||
|
onOk()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewInvoice.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool,
|
||||||
|
defaultValues: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewInvoice
|
||||||
@ -10,6 +10,7 @@ const breadcrumbNameMap = {
|
|||||||
inventory: 'Inventory',
|
inventory: 'Inventory',
|
||||||
management: 'Management',
|
management: 'Management',
|
||||||
developer: 'Developer',
|
developer: 'Developer',
|
||||||
|
finance: 'Finance',
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
info: 'Info',
|
info: 'Info',
|
||||||
design: 'Design',
|
design: 'Design',
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
|
|||||||
import MenuIcon from '../../Icons/MenuIcon'
|
import MenuIcon from '../../Icons/MenuIcon'
|
||||||
import ProductionIcon from '../../Icons/ProductionIcon'
|
import ProductionIcon from '../../Icons/ProductionIcon'
|
||||||
import InventoryIcon from '../../Icons/InventoryIcon'
|
import InventoryIcon from '../../Icons/InventoryIcon'
|
||||||
|
import FinanceIcon from '../../Icons/FinanceIcon'
|
||||||
import PersonIcon from '../../Icons/PersonIcon'
|
import PersonIcon from '../../Icons/PersonIcon'
|
||||||
import CloudIcon from '../../Icons/CloudIcon'
|
import CloudIcon from '../../Icons/CloudIcon'
|
||||||
import BellIcon from '../../Icons/BellIcon'
|
import BellIcon from '../../Icons/BellIcon'
|
||||||
@ -69,6 +70,11 @@ const DashboardNavigation = () => {
|
|||||||
label: 'Inventory',
|
label: 'Inventory',
|
||||||
icon: <InventoryIcon />
|
icon: <InventoryIcon />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'finance',
|
||||||
|
label: 'Finance',
|
||||||
|
icon: <FinanceIcon />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'management',
|
key: 'management',
|
||||||
label: 'Management',
|
label: 'Management',
|
||||||
@ -130,6 +136,8 @@ const DashboardNavigation = () => {
|
|||||||
navigate('/dashboard/production/overview')
|
navigate('/dashboard/production/overview')
|
||||||
} else if (key === 'inventory') {
|
} else if (key === 'inventory') {
|
||||||
navigate('/dashboard/inventory/overview')
|
navigate('/dashboard/inventory/overview')
|
||||||
|
} else if (key === 'finance') {
|
||||||
|
navigate('/dashboard/finance/overview')
|
||||||
} else if (key === 'management') {
|
} else if (key === 'management') {
|
||||||
navigate('/dashboard/management/filaments')
|
navigate('/dashboard/management/filaments')
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/components/Icons/InvoiceIcon.jsx
Normal file
9
src/components/Icons/InvoiceIcon.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/invoiceicon.svg?react'
|
||||||
|
|
||||||
|
const InvoiceIcon = (props) => (
|
||||||
|
<Icon component={CustomIconSvg} {...props} />
|
||||||
|
)
|
||||||
|
|
||||||
|
export default InvoiceIcon
|
||||||
|
|
||||||
336
src/database/models/Invoice.js
Normal file
336
src/database/models/Invoice.js
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
import InvoiceIcon from '../../components/Icons/InvoiceIcon'
|
||||||
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
|
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||||
|
import EditIcon from '../../components/Icons/EditIcon'
|
||||||
|
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||||
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
|
export const Invoice = {
|
||||||
|
name: 'invoice',
|
||||||
|
label: 'Invoice',
|
||||||
|
prefix: 'INV',
|
||||||
|
icon: InvoiceIcon,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
label: 'Info',
|
||||||
|
default: true,
|
||||||
|
row: true,
|
||||||
|
icon: InfoCircleIcon,
|
||||||
|
url: (_id) => `/dashboard/finance/invoices/info?invoiceId=${_id}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
label: 'Edit',
|
||||||
|
type: 'button',
|
||||||
|
icon: EditIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=edit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
},
|
||||||
|
disabled: (objectData) => {
|
||||||
|
return objectData?.state?.type != 'draft'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelEdit',
|
||||||
|
label: 'Cancel Edit',
|
||||||
|
type: 'button',
|
||||||
|
icon: XMarkIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=cancelEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finishEdit',
|
||||||
|
label: 'Finish Edit',
|
||||||
|
type: 'button',
|
||||||
|
icon: CheckIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=finishEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
label: 'Delete',
|
||||||
|
type: 'button',
|
||||||
|
icon: BinIcon,
|
||||||
|
danger: true,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=delete`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
},
|
||||||
|
disabled: (objectData) => {
|
||||||
|
return objectData?.state?.type != 'draft'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
name: 'send',
|
||||||
|
label: 'Send',
|
||||||
|
type: 'button',
|
||||||
|
icon: CheckIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=send`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?.state?.type == 'draft'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'markPaid',
|
||||||
|
label: 'Mark Paid',
|
||||||
|
type: 'button',
|
||||||
|
icon: CheckIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=markPaid`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return (
|
||||||
|
objectData?.state?.type == 'sent' ||
|
||||||
|
objectData?.state?.type == 'partiallyPaid'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancel',
|
||||||
|
label: 'Cancel',
|
||||||
|
type: 'button',
|
||||||
|
icon: XMarkIcon,
|
||||||
|
danger: true,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=cancel`,
|
||||||
|
disabled: (objectData) => {
|
||||||
|
return (
|
||||||
|
objectData?.state?.type == 'cancelled' ||
|
||||||
|
objectData?.state?.type == 'paid'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
visible: (objectData) => {
|
||||||
|
return (
|
||||||
|
objectData?.state?.type == 'draft' ||
|
||||||
|
objectData?.state?.type == 'sent'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
group: ['vendor', 'customer', 'invoiceType'],
|
||||||
|
filters: ['vendor', 'customer', 'invoiceType'],
|
||||||
|
sorters: ['createdAt', 'state', 'updatedAt', 'invoiceDate', 'dueDate'],
|
||||||
|
columns: [
|
||||||
|
'_id',
|
||||||
|
'_reference',
|
||||||
|
'state',
|
||||||
|
'invoiceType',
|
||||||
|
'vendor',
|
||||||
|
'customer',
|
||||||
|
'invoiceDate',
|
||||||
|
'dueDate',
|
||||||
|
'totalAmount',
|
||||||
|
'totalAmountWithTax',
|
||||||
|
'totalTaxAmount',
|
||||||
|
'shippingAmount',
|
||||||
|
'shippingAmountWithTax',
|
||||||
|
'grandTotalAmount',
|
||||||
|
'createdAt',
|
||||||
|
'updatedAt'
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
columnFixed: 'left',
|
||||||
|
objectType: 'invoice',
|
||||||
|
columnWidth: 140,
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '_reference',
|
||||||
|
label: 'Reference',
|
||||||
|
type: 'reference',
|
||||||
|
required: true,
|
||||||
|
objectType: 'invoice',
|
||||||
|
showCopy: true,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
||||||
|
{
|
||||||
|
name: 'invoiceDate',
|
||||||
|
label: 'Invoice Date',
|
||||||
|
type: 'date',
|
||||||
|
readOnly: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dueDate',
|
||||||
|
label: 'Due Date',
|
||||||
|
type: 'date',
|
||||||
|
readOnly: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor',
|
||||||
|
label: 'Vendor',
|
||||||
|
required: true,
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'vendor',
|
||||||
|
showHyperlink: true,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?.invoiceType === 'purchase' || objectData?.vendor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'orderType',
|
||||||
|
label: 'Order Type',
|
||||||
|
type: 'objectType',
|
||||||
|
masterFilter: ['purchaseOrder', 'salesOrder'],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'order',
|
||||||
|
label: 'Order',
|
||||||
|
type: 'object',
|
||||||
|
objectType: (objectData) => {
|
||||||
|
return objectData?.orderType
|
||||||
|
},
|
||||||
|
masterFilter: (objectData) => {
|
||||||
|
return {
|
||||||
|
vendor: objectData?.vendor?._id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sentAt',
|
||||||
|
label: 'Sent At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'paidAt',
|
||||||
|
label: 'Paid At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelledAt',
|
||||||
|
label: 'Cancelled At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'overdueAt',
|
||||||
|
label: 'Overdue At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalTaxAmount',
|
||||||
|
label: 'Total Tax Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalAmountWithTax',
|
||||||
|
label: 'Total Amount w/ Tax',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175,
|
||||||
|
roundNumber: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shippingAmount',
|
||||||
|
label: 'Shipping Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shippingAmountWithTax',
|
||||||
|
label: 'Shipping Amount w/ Tax',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
readOnly: true,
|
||||||
|
roundNumber: 2,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalAmount',
|
||||||
|
label: 'Total Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grandTotalAmount',
|
||||||
|
label: 'Grand Total Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
columnWidth: 175,
|
||||||
|
readOnly: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
stats: [
|
||||||
|
{
|
||||||
|
name: 'draft.count',
|
||||||
|
label: 'Draft',
|
||||||
|
type: 'number',
|
||||||
|
color: 'default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sent.count',
|
||||||
|
label: 'Sent',
|
||||||
|
type: 'number',
|
||||||
|
color: 'cyan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'partiallyPaid.count',
|
||||||
|
label: 'Partially Paid',
|
||||||
|
type: 'number',
|
||||||
|
color: 'processing'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'paid.count',
|
||||||
|
label: 'Paid',
|
||||||
|
type: 'number',
|
||||||
|
color: 'success'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'overdue.count',
|
||||||
|
label: 'Overdue',
|
||||||
|
type: 'number',
|
||||||
|
color: 'error'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelled.count',
|
||||||
|
label: 'Cancelled',
|
||||||
|
type: 'number',
|
||||||
|
color: 'default'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
29
src/routes/FinanceRoutes.jsx
Normal file
29
src/routes/FinanceRoutes.jsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { lazy } from 'react'
|
||||||
|
import { Route } from 'react-router-dom'
|
||||||
|
|
||||||
|
const Invoices = lazy(
|
||||||
|
() => import('../components/Dashboard/Finance/Invoices.jsx')
|
||||||
|
)
|
||||||
|
const InvoiceInfo = lazy(
|
||||||
|
() => import('../components/Dashboard/Finance/Invoices/InvoiceInfo.jsx')
|
||||||
|
)
|
||||||
|
const FinanceOverview = lazy(
|
||||||
|
() => import('../components/Dashboard/Finance/FinanceOverview.jsx')
|
||||||
|
)
|
||||||
|
|
||||||
|
const FinanceRoutes = [
|
||||||
|
<Route
|
||||||
|
key='overview'
|
||||||
|
path='finance/overview'
|
||||||
|
element={<FinanceOverview />}
|
||||||
|
/>,
|
||||||
|
<Route key='invoices' path='finance/invoices' element={<Invoices />} />,
|
||||||
|
<Route
|
||||||
|
key='invoices-info'
|
||||||
|
path='finance/invoices/info'
|
||||||
|
element={<InvoiceInfo />}
|
||||||
|
/>
|
||||||
|
]
|
||||||
|
|
||||||
|
export default FinanceRoutes
|
||||||
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export { default as ProductionRoutes } from './ProductionRoutes'
|
export { default as ProductionRoutes } from './ProductionRoutes'
|
||||||
export { default as InventoryRoutes } from './InventoryRoutes'
|
export { default as InventoryRoutes } from './InventoryRoutes'
|
||||||
|
export { default as FinanceRoutes } from './FinanceRoutes'
|
||||||
export { default as ManagementRoutes } from './ManagementRoutes'
|
export { default as ManagementRoutes } from './ManagementRoutes'
|
||||||
export { default as DeveloperRoutes } from './DeveloperRoutes'
|
export { default as DeveloperRoutes } from './DeveloperRoutes'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user