- 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.
241 lines
7.0 KiB
JavaScript
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
|