Tom Butcher 9d1a032e10 Refactor Dashboard Components to Remove Unused State Management
- Removed unnecessary useState hooks for view state management in multiple dashboard components (Invoices, PaymentPolicies, Payments, TaxRecords, FilamentStocks, OrderItems, PartStocks, ProductStocks, PurchaseOrders, Shipments, StockAudits, StockEvents, StockLocations, StockTransfers, AppPasswords, AuditLogs, Couriers, CourierServices, DocumentJobs, DocumentPrinters, DocumentSizes, DocumentTemplates, Filaments, FilamentSkus).
- Updated ObjectListViewProvider integration to streamline component functionality and improve maintainability.
- Adjusted CSS variables in App.css for enhanced styling consistency across components.
2026-09-02 22:41:49 +01:00

321 lines
7.9 KiB
JavaScript

import PropTypes from 'prop-types'
import React, { useEffect, useMemo, useState } from 'react'
import {
Input,
Dropdown,
Checkbox,
Button,
Flex,
Typography,
Divider,
theme
} from 'antd'
const { useToken } = theme
import SegmentedNav, { SegmentedNavDragHandle } from './SegmentedNav'
import { CaretDownFilled, LoadingOutlined } from '@ant-design/icons'
import { getModelByName } from '../../../database/ObjectModels'
import { useObjectListView } from '../context/ObjectListViewContext'
import classNames from 'classnames'
import PlusIcon from '../../Icons/PlusIcon'
import BinIcon from '../../Icons/BinIcon'
import ScrollBox from './ScrollBox'
import ColorSelector from './ColorSelector'
const { Text } = Typography
const ViewColorDot = ({ color }) => (
<span
className='list-view-tabs-color-dot'
style={{ backgroundColor: color || '#3498DB' }}
/>
)
ViewColorDot.propTypes = {
color: PropTypes.string
}
const ViewOptionsDropdown = ({ view, isEditing, onChange, onDelete }) => {
const menu = {
items: [
{
key: 'delete',
label: 'Delete',
icon: <BinIcon />,
danger: true,
onClick: ({ domEvent }) => {
domEvent.stopPropagation()
onDelete?.()
}
},
{ type: 'divider' }
]
}
const menuStyle = {
boxShadow: 'none'
}
const { token } = useToken()
const contentStyle = {
backgroundColor: token.colorBgElevated,
borderRadius: token.borderRadiusLG,
boxShadow: token.boxShadowSecondary
}
return (
<Dropdown
menu={menu}
trigger={['hover']}
placement='bottomLeft'
popupRender={(menu) => (
<div className='list-view-tabs-options-dropdown' style={contentStyle}>
{React.cloneElement(menu, { style: menuStyle })}
<Flex
vertical
gap='middle'
className='list-view-tabs-options-dropdown-privacy'
>
<Checkbox
checked={view.private !== false}
disabled={!isEditing}
onChange={(event) => onChange({ private: event.target.checked })}
onClick={(event) => event.stopPropagation()}
>
Private
</Checkbox>
</Flex>
</div>
)}
>
<Button
type='text'
size='small'
className='list-view-tabs-options-button'
icon={<CaretDownFilled style={{ fontSize: 14, marginTop: 4 }} />}
disabled={!isEditing}
onClick={(event) => event.stopPropagation()}
/>
</Dropdown>
)
}
ViewOptionsDropdown.propTypes = {
view: PropTypes.object.isRequired,
isEditing: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
onDelete: PropTypes.func
}
const ViewTabNameInput = ({ value, onClick, onChange }) => (
<span className='list-view-tabs-name-input-wrap'>
<span aria-hidden className='list-view-tabs-name-input-sizer'>
{value}
</span>
<Input
size='small'
variant='borderless'
className='list-view-tabs-name-input'
value={value}
onClick={onClick}
onChange={onChange}
/>
</span>
)
ViewTabNameInput.propTypes = {
value: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired
}
const ViewTabLabel = ({
view,
isEditing,
onDraftChange,
onRemove,
onSelect
}) => {
if (!isEditing) {
return (
<Flex align='center' gap={8} className='list-view-tabs-label'>
<ViewColorDot color={view.color} />
<Text ellipsis style={{ maxWidth: 160 }}>
{view.name}
</Text>
</Flex>
)
}
return (
<Flex
align='center'
gap={6}
className='list-view-tabs-label list-view-tabs-label-editing'
onFocusCapture={() => onSelect?.()}
>
<div style={{ marginRight: 2 }}>
<ColorSelector
size='small'
showText={false}
required
value={view.color || '#3498DB'}
onChange={(color) => onDraftChange({ color })}
onClick={(event) => event.stopPropagation()}
/>
</div>
<ViewTabNameInput
value={view.name}
onClick={(event) => event.stopPropagation()}
onChange={(event) => onDraftChange({ name: event.target.value })}
/>
<Flex align='center' gap={2}>
<ViewOptionsDropdown
view={view}
isEditing={isEditing}
onChange={onDraftChange}
onDelete={onRemove}
/>
<SegmentedNavDragHandle value={String(view._id)} />
</Flex>
</Flex>
)
}
ViewTabLabel.propTypes = {
view: PropTypes.object.isRequired,
isEditing: PropTypes.bool.isRequired,
onDraftChange: PropTypes.func.isRequired,
onRemove: PropTypes.func,
onSelect: PropTypes.func
}
const ListViewTabs = () => {
const {
objectType,
ALL_TAB_KEY,
views,
activeTabKey,
initialLoading,
isEditing,
selectTab,
handleTabEdit,
updateDraftView,
reorderDraftViews,
viewTabSignatures
} = useObjectListView()
const [animated, setAnimated] = useState(false)
useEffect(() => {
if (initialLoading) {
setAnimated(false)
return undefined
}
const timeoutId = setTimeout(() => setAnimated(true), 10)
return () => clearTimeout(timeoutId)
}, [initialLoading])
const model = getModelByName(objectType)
const options = useMemo(() => {
const Icon = initialLoading ? LoadingOutlined : model?.icon
const styles = initialLoading
? { fontSize: 12, color: 'var(--color-button-border)' }
: { fontSize: 14 }
const allOption = {
value: ALL_TAB_KEY,
icon: Icon ? (
<Icon
style={styles}
color={'secondary'}
spin={initialLoading || undefined}
/>
) : null
}
const viewOptions = views.map((view) => ({
value: String(view._id),
reorderable: isEditing,
label: (
<ViewTabLabel
view={view}
isEditing={isEditing}
onDraftChange={(changes) => updateDraftView(view._id, changes)}
onRemove={
isEditing
? () => handleTabEdit(String(view._id), 'remove')
: undefined
}
onSelect={() => {
const tabKey = String(view._id)
if (activeTabKey !== tabKey) {
selectTab(tabKey)
}
}}
/>
)
}))
return [allOption, ...viewOptions]
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
ALL_TAB_KEY,
activeTabKey,
handleTabEdit,
initialLoading,
isEditing,
model?.icon,
selectTab,
updateDraftView,
viewTabSignatures
])
return (
<Flex
align='center'
gap='small'
style={{ minHeight: 0, width: '100%', minWidth: 0 }}
>
<Divider type='vertical' style={{ margin: '0 4px', height: '18px' }} />
<Flex
align='center'
gap={4}
className={classNames('list-view-tabs', {
'list-view-tabs-editing': isEditing
})}
style={{ flex: 1, minWidth: 0 }}
>
<ScrollBox className='list-view-tabs-segmented-wrap' horizontal>
<Flex
align='center'
gap={8}
className='list-view-tabs-segmented-inner'
>
<SegmentedNav
className='list-view-tabs-segmented'
value={activeTabKey}
options={options}
onChange={selectTab}
onReorder={isEditing ? reorderDraftViews : undefined}
animated={animated}
/>
{isEditing && (
<Button
type='text'
size='small'
className='list-view-tabs-add-button'
onClick={() => handleTabEdit(null, 'add')}
icon={<PlusIcon style={{ fontSize: 14, marginTop: 3 }} />}
/>
)}
</Flex>
</ScrollBox>
</Flex>
</Flex>
)
}
export default ListViewTabs