farmcontrol-ui/src/components/Dashboard/common/ObjectTableViewButton.jsx
Tom Butcher 5e6b4213ab Enhance ObjectKanban and ObjectTable Components with Column Resizing and Drag-and-Drop Functionality
- Implemented dynamic column resizing in ObjectKanban and ObjectKanbanColumn, allowing users to adjust column widths for better layout control.
- Added drag-and-drop support for reordering columns in ObjectKanbanHeader, improving user interaction and customization of the kanban view.
- Updated CSS styles in App.css to accommodate new resizing handles and visual feedback during drag-and-drop actions.
- Refactored ObjectKanban and related components to manage column states and widths effectively, ensuring a responsive and user-friendly experience.
2026-09-03 14:27:34 +01:00

241 lines
7.0 KiB
JavaScript

import PropTypes from 'prop-types'
import { useContext, useMemo, useState } from 'react'
import ObjectListViewContext from '../context/ObjectListViewContext'
import {
Button,
Divider,
Flex,
Modal,
Popover,
Radio,
Select,
Typography
} from 'antd'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import KanbanIcon from '../../Icons/KanbanIcon'
import SettingsIcon from '../../Icons/SettingsIcon'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import { getModelByName } from '../../../database/ObjectModels'
import {
getViewModeType,
isKanbanCategoryProperty,
normalizeViewMode
} from './viewModeUtils'
const VIEW_MODE_OPTIONS = [
{ type: 'list', label: 'List' },
{ type: 'cards', label: 'Cards' },
{ type: 'kanban', label: 'Kanban' }
]
const { Text } = Typography
const ObjectTableViewButton = ({
objectType,
viewMode: viewModeProp,
setViewMode: setViewModeProp,
showEndingDivider = false,
...buttonProps
}) => {
const objectListView = useContext(ObjectListViewContext)
const viewMode = viewModeProp ?? objectListView?.viewMode
const setViewMode = setViewModeProp ?? objectListView?.setViewMode
const [settingsOpen, setSettingsOpen] = useState(false)
const normalizedViewMode = normalizeViewMode(viewMode)
const model = getModelByName(objectType)
const categoryProperties = useMemo(
() =>
model?.properties?.filter((property) =>
isKanbanCategoryProperty(property)
) || [],
[model]
)
const hasKanbanOption = categoryProperties.length > 0
const defaultCategoryProperty =
categoryProperties.find((property) => property.type === 'state')?.name ||
categoryProperties[0]?.name
const availableViewModes = useMemo(
() =>
VIEW_MODE_OPTIONS.filter(
(option) => option.type !== 'kanban' || hasKanbanOption
).map((option) => option.type),
[hasKanbanOption]
)
const handleTypeChange = (nextType) => {
if (nextType === 'kanban') {
const categoryProperty =
normalizedViewMode.type === 'kanban'
? normalizedViewMode.settings?.categoryProperty ||
defaultCategoryProperty
: defaultCategoryProperty
const nextMode = {
type: 'kanban',
settings: { categoryProperty }
}
if (
normalizedViewMode.type === 'kanban' &&
Array.isArray(normalizedViewMode.settings?.columns)
) {
nextMode.settings.columns = normalizedViewMode.settings.columns
}
setViewMode(nextMode)
return
}
setViewMode({ type: nextType })
}
const handleCategoryPropertyChange = (categoryProperty) => {
setViewMode({
type: 'kanban',
settings: { categoryProperty }
})
}
const { onClick: buttonOnClick, ...restButtonProps } = buttonProps
const handleCycleView = (event) => {
buttonOnClick?.(event)
const currentType = getViewModeType(normalizedViewMode)
const currentIndex = availableViewModes.indexOf(currentType)
const nextIndex =
currentIndex === -1 ? 0 : (currentIndex + 1) % availableViewModes.length
handleTypeChange(availableViewModes[nextIndex])
}
const activeIcon =
normalizedViewMode.type === 'cards' ? (
<GridIcon />
) : normalizedViewMode.type === 'kanban' ? (
<KanbanIcon />
) : (
<ListIcon />
)
const content = (
<>
<Popover
content={
<Flex vertical>
<Radio.Group
value={getViewModeType(normalizedViewMode)}
onChange={(e) => handleTypeChange(e.target.value)}
>
<Flex vertical gap='middle' style={{ margin: '4px 8px' }}>
{VIEW_MODE_OPTIONS.filter(
(option) => option.type !== 'kanban' || hasKanbanOption
).map((option) => (
<Radio key={option.type} value={option.type}>
<Flex
gap='8px'
align='center'
style={{ marginLeft: '4px' }}
>
<Text>{option.label}</Text>
{option.type === 'kanban' && (
<Button
type='text'
size='small'
icon={
<SettingsIcon
style={{ fontSize: '14px', marginTop: '2.5px' }}
/>
}
disabled={normalizedViewMode.type !== 'kanban'}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setSettingsOpen(true)
}}
/>
)}
</Flex>
</Radio>
))}
</Flex>
</Radio.Group>
</Flex>
}
placement='bottomLeft'
arrow={false}
trigger='hover'
>
<Button
icon={activeIcon}
title='View mode'
onClick={handleCycleView}
{...restButtonProps}
/>
</Popover>
<Modal
open={settingsOpen}
onCancel={() => setSettingsOpen(false)}
destroyOnHidden
focusTriggerAfterClose={false}
footer={null}
centered
closeIcon={null}
getContainer={() => document.body}
width={520}
>
<Flex vertical gap='middle' onMouseDown={(event) => event.stopPropagation()}>
<Flex gap='middle'>
<InfoCircleIcon />
<Text strong>Kanban settings</Text>
</Flex>
<Text>
Select the property used to group cards into columns:
</Text>
<Select
style={{ width: '100%' }}
placeholder='Select category property'
value={
normalizedViewMode.settings?.categoryProperty ||
defaultCategoryProperty
}
onChange={handleCategoryPropertyChange}
options={categoryProperties.map((property) => ({
value: property.name,
label: property.label || property.name
}))}
/>
<Flex justify='end' gap='small'>
<Button type='default' onClick={() => setSettingsOpen(false)}>
Cancel
</Button>
<Button type='primary' onClick={() => setSettingsOpen(false)}>
Apply
</Button>
</Flex>
</Flex>
</Modal>
</>
)
if (showEndingDivider) {
return (
<Flex gap='small' align='center'>
<Divider type='vertical' style={{ margin: '0 4px', height: '18px' }} />
{content}
</Flex>
)
}
return content
}
ObjectTableViewButton.propTypes = {
objectType: PropTypes.string.isRequired,
viewMode: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
setViewMode: PropTypes.func,
showEndingDivider: PropTypes.bool
}
export default ObjectTableViewButton