- 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.
321 lines
7.9 KiB
JavaScript
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
|