Compare commits
4 Commits
8f369d777d
...
bef3e47d29
| Author | SHA1 | Date | |
|---|---|---|---|
| bef3e47d29 | |||
| 6cc3fdb0ce | |||
| 27f5989eb8 | |||
| 476a01eafb |
@ -82,7 +82,7 @@ const ExportListButton = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rss',
|
key: 'rss',
|
||||||
label: 'RSS Feed Connection',
|
label: 'RSS Feed',
|
||||||
icon: <RssIcon />,
|
icon: <RssIcon />,
|
||||||
onClick: () => setRssModalOpen(true)
|
onClick: () => setRssModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,7 +136,7 @@ const FilterSidebar = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card style={{ width: '25%', flexShrink: 0, height: '100%' }}>
|
<Card style={{ flexShrink: 0, height: '100%' }}>
|
||||||
<Flex vertical gap='middle'>
|
<Flex vertical gap='middle'>
|
||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Dropdown menu={menuItems} trigger={['hover']} placement='bottomLeft'>
|
<Dropdown menu={menuItems} trigger={['hover']} placement='bottomLeft'>
|
||||||
@ -158,14 +158,14 @@ const FilterSidebar = ({
|
|||||||
value={row.field || undefined}
|
value={row.field || undefined}
|
||||||
onChange={(v) => changeField(row.field, v)}
|
onChange={(v) => changeField(row.field, v)}
|
||||||
options={availableOptions(row.field)}
|
options={availableOptions(row.field)}
|
||||||
style={{ minWidth: 80, flex: 1 }}
|
style={{ minWidth: 80 }}
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder='Value'
|
placeholder='Value'
|
||||||
value={row.value}
|
value={row.value}
|
||||||
onChange={(e) => changeValue(row.field, e.target.value)}
|
onChange={(e) => changeValue(row.field, e.target.value)}
|
||||||
style={{ width: 160 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon={<CloseOutlined />}
|
icon={<CloseOutlined />}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useMemo, useEffect, useRef } from 'react'
|
import { useMemo, useEffect, useRef, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Table, Skeleton, Card, Button, Flex, Typography } from 'antd'
|
import { Table, Skeleton, Card, Button, Flex, Typography, Modal } from 'antd'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import ObjectProperty from './ObjectProperty'
|
import ObjectProperty from './ObjectProperty'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import BinIcon from '../../Icons/BinIcon'
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
const { Text } = Typography
|
const { Text, Link, Title } = Typography
|
||||||
|
|
||||||
const DEFAULT_COLUMN_WIDTHS = {
|
const DEFAULT_COLUMN_WIDTHS = {
|
||||||
text: 200,
|
text: 200,
|
||||||
@ -64,10 +64,44 @@ const ObjectChildTable = ({
|
|||||||
value = [],
|
value = [],
|
||||||
rollups = [],
|
rollups = [],
|
||||||
onChange,
|
onChange,
|
||||||
|
minimal = false,
|
||||||
|
label = '',
|
||||||
...tableProps
|
...tableProps
|
||||||
}) => {
|
}) => {
|
||||||
const mainTableWrapperRef = useRef(null)
|
const mainTableWrapperRef = useRef(null)
|
||||||
const rollupTableWrapperRef = useRef(null)
|
const rollupTableWrapperRef = useRef(null)
|
||||||
|
const generatedRowKeysRef = useRef(new WeakMap())
|
||||||
|
const generatedRowKeyCountRef = useRef(0)
|
||||||
|
const [minimalModelOpen, setMinimalModelOpen] = useState(false)
|
||||||
|
const getFallbackRowKey = (record) => {
|
||||||
|
if (!record || typeof record !== 'object') {
|
||||||
|
return `object-child-table-row-${String(record)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record._objectChildTableKey != null) {
|
||||||
|
return record._objectChildTableKey
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = generatedRowKeysRef.current.get(record)
|
||||||
|
if (existing) return existing
|
||||||
|
|
||||||
|
const generated = `object-child-table-row-${generatedRowKeyCountRef.current}`
|
||||||
|
generatedRowKeyCountRef.current += 1
|
||||||
|
generatedRowKeysRef.current.set(record, generated)
|
||||||
|
return generated
|
||||||
|
}
|
||||||
|
|
||||||
|
const getResolvedRecordKey = (record) => {
|
||||||
|
if (typeof rowKey === 'function') {
|
||||||
|
return rowKey(record) ?? getFallbackRowKey(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof rowKey === 'string' && rowKey.length > 0) {
|
||||||
|
return record?.[rowKey] ?? getFallbackRowKey(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFallbackRowKey(record)
|
||||||
|
}
|
||||||
|
|
||||||
const propertyMap = useMemo(() => {
|
const propertyMap = useMemo(() => {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
@ -130,10 +164,14 @@ const ObjectChildTable = ({
|
|||||||
const currentItems = Array.isArray(itemsSource)
|
const currentItems = Array.isArray(itemsSource)
|
||||||
? [...itemsSource]
|
? [...itemsSource]
|
||||||
: []
|
: []
|
||||||
|
const existingRowKey = getResolvedRecordKey(record)
|
||||||
const updatedItem = {
|
const updatedItem = {
|
||||||
...currentItems[index],
|
...currentItems[index],
|
||||||
[property.name]: resolved
|
[property.name]: resolved
|
||||||
}
|
}
|
||||||
|
// Preserve fallback row identity across immutable updates so the row
|
||||||
|
// is not remounted while typing (which causes input focus loss).
|
||||||
|
generatedRowKeysRef.current.set(updatedItem, existingRowKey)
|
||||||
currentItems[index] = updatedItem
|
currentItems[index] = updatedItem
|
||||||
if (typeof onChange === 'function') {
|
if (typeof onChange === 'function') {
|
||||||
onChange(currentItems)
|
onChange(currentItems)
|
||||||
@ -186,10 +224,11 @@ const ObjectChildTable = ({
|
|||||||
(item) => item[rowKey] !== record[rowKey]
|
(item) => item[rowKey] !== record[rowKey]
|
||||||
)
|
)
|
||||||
} else if (typeof rowKey === 'function') {
|
} else if (typeof rowKey === 'function') {
|
||||||
// If rowKey is a function, find the item by comparing the resolved keys
|
// If rowKey is a function, find the item by comparing resolved keys.
|
||||||
const recordKey = rowKey(record, index)
|
// Ant Design deprecates index-based rowKey callbacks.
|
||||||
newItems = currentItems.filter((item, i) => {
|
const recordKey = getResolvedRecordKey(record)
|
||||||
const itemKey = rowKey(item, i)
|
newItems = currentItems.filter((item) => {
|
||||||
|
const itemKey = getResolvedRecordKey(item)
|
||||||
return itemKey !== recordKey
|
return itemKey !== recordKey
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -216,6 +255,7 @@ const ObjectChildTable = ({
|
|||||||
resolvedProperties,
|
resolvedProperties,
|
||||||
additionalColumns,
|
additionalColumns,
|
||||||
isEditing,
|
isEditing,
|
||||||
|
canAddRemove,
|
||||||
itemsSource,
|
itemsSource,
|
||||||
onChange,
|
onChange,
|
||||||
rowKey
|
rowKey
|
||||||
@ -236,8 +276,7 @@ const ObjectChildTable = ({
|
|||||||
return itemsSource
|
return itemsSource
|
||||||
}, [itemsSource, loading, skeletonData])
|
}, [itemsSource, loading, skeletonData])
|
||||||
|
|
||||||
const resolvedRowKey =
|
const resolvedRowKey = (record) => getResolvedRecordKey(record)
|
||||||
typeof rowKey === 'function' ? rowKey : (_record, index) => index
|
|
||||||
|
|
||||||
const scrollConfig =
|
const scrollConfig =
|
||||||
scrollHeight != null
|
scrollHeight != null
|
||||||
@ -281,7 +320,9 @@ const ObjectChildTable = ({
|
|||||||
|
|
||||||
// Single summary row where each rollup value is placed under
|
// Single summary row where each rollup value is placed under
|
||||||
// the column that matches its `property` field.
|
// the column that matches its `property` field.
|
||||||
const summaryRow = {}
|
const summaryRow = {
|
||||||
|
_objectChildTableKey: 'object-child-table-rollup-summary'
|
||||||
|
}
|
||||||
|
|
||||||
properties.forEach((property) => {
|
properties.forEach((property) => {
|
||||||
const rollup = rollups.find(
|
const rollup = rollups.find(
|
||||||
@ -354,7 +395,7 @@ const ObjectChildTable = ({
|
|||||||
...propertyColumns,
|
...propertyColumns,
|
||||||
...(blankDeleteColumn ? [blankDeleteColumn] : [])
|
...(blankDeleteColumn ? [blankDeleteColumn] : [])
|
||||||
]
|
]
|
||||||
}, [properties, rollups, isEditing])
|
}, [properties, rollups, isEditing, canAddRemove])
|
||||||
|
|
||||||
const hasRollups = useMemo(
|
const hasRollups = useMemo(
|
||||||
() => Array.isArray(rollups) && rollups.length > 0,
|
() => Array.isArray(rollups) && rollups.length > 0,
|
||||||
@ -414,13 +455,14 @@ const ObjectChildTable = ({
|
|||||||
dataSource={rollupDataSource}
|
dataSource={rollupDataSource}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
columns={rollupColumns}
|
columns={rollupColumns}
|
||||||
loading={{ spinning: loading, indicator: <></> }}
|
loading={{ spinning: loading, indicator: null }}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size={size}
|
size={size}
|
||||||
rowKey={resolvedRowKey}
|
rowKey={resolvedRowKey}
|
||||||
scroll={scrollConfig}
|
scroll={scrollConfig}
|
||||||
locale={{ emptyText }}
|
locale={{ emptyText }}
|
||||||
style={{ maxWidth, minWidth: 0 }}
|
bordered={true}
|
||||||
|
style={{ maxWidth: minimal ? '100%' : maxWidth, minWidth: 0 }}
|
||||||
className='rollup-table'
|
className='rollup-table'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -430,13 +472,14 @@ const ObjectChildTable = ({
|
|||||||
<Flex vertical>
|
<Flex vertical>
|
||||||
<div ref={mainTableWrapperRef}>
|
<div ref={mainTableWrapperRef}>
|
||||||
<Table
|
<Table
|
||||||
style={{ maxWidth, minWidth: 0 }}
|
style={{ maxWidth: minimal ? '100%' : maxWidth, minWidth: 0 }}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
||||||
size={size}
|
size={size}
|
||||||
rowKey={resolvedRowKey}
|
rowKey={resolvedRowKey}
|
||||||
scroll={scrollConfig}
|
scroll={scrollConfig}
|
||||||
|
bordered={true}
|
||||||
locale={{ emptyText }}
|
locale={{ emptyText }}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
||||||
@ -469,6 +512,34 @@ const ObjectChildTable = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (minimal == true) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
setMinimalModelOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value?.length || 0} {value?.length == 1 ? 'item' : 'items'}
|
||||||
|
</Link>
|
||||||
|
<Modal
|
||||||
|
open={minimalModelOpen}
|
||||||
|
onCancel={() => setMinimalModelOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
width='860px'
|
||||||
|
>
|
||||||
|
<Title
|
||||||
|
level={2}
|
||||||
|
style={{ marginTop: 0, lineHeight: '0.7', marginBottom: 20 }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Title>
|
||||||
|
{tableComponent}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return tableComponent
|
return tableComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,13 +561,15 @@ ObjectChildTable.propTypes = {
|
|||||||
skeletonRows: PropTypes.number,
|
skeletonRows: PropTypes.number,
|
||||||
additionalColumns: PropTypes.arrayOf(PropTypes.object),
|
additionalColumns: PropTypes.arrayOf(PropTypes.object),
|
||||||
emptyText: PropTypes.node,
|
emptyText: PropTypes.node,
|
||||||
|
label: PropTypes.string,
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
value: PropTypes.arrayOf(PropTypes.object),
|
value: PropTypes.arrayOf(PropTypes.object),
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
maxWidth: PropTypes.string,
|
maxWidth: PropTypes.string,
|
||||||
rollups: PropTypes.arrayOf(PropTypes.object),
|
rollups: PropTypes.arrayOf(PropTypes.object),
|
||||||
objectData: PropTypes.object,
|
objectData: PropTypes.object,
|
||||||
canAddRemove: PropTypes.bool
|
canAddRemove: PropTypes.bool,
|
||||||
|
minimal: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectChildTable
|
export default ObjectChildTable
|
||||||
|
|||||||
@ -422,6 +422,8 @@ const ObjectProperty = ({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
rollups={rollups}
|
rollups={rollups}
|
||||||
size={size}
|
size={size}
|
||||||
|
minimal={minimal}
|
||||||
|
label={label}
|
||||||
canAddRemove={canAddRemove}
|
canAddRemove={canAddRemove}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Space,
|
Space,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Form
|
Form,
|
||||||
|
Splitter
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
@ -940,50 +941,49 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
const tableContent = (
|
const tableContent = (
|
||||||
<Flex gap={'middle'} vertical style={{ flex: 1, minWidth: 0 }}>
|
<Flex gap={'middle'} vertical style={{ flex: 1, minWidth: 0 }}>
|
||||||
{cards ? (
|
<Splitter className={'farmcontrol-splitter'}>
|
||||||
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
<Splitter.Panel>
|
||||||
{renderCards()}
|
{cards ? (
|
||||||
</Spin>
|
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
||||||
) : (
|
{renderCards()}
|
||||||
<Table
|
</Spin>
|
||||||
ref={tableRef}
|
) : (
|
||||||
dataSource={tableData}
|
<Table
|
||||||
columns={columnsWithSkeleton}
|
ref={tableRef}
|
||||||
className='dashboard-table'
|
dataSource={tableData}
|
||||||
pagination={false}
|
columns={columnsWithSkeleton}
|
||||||
scroll={{ y: adjustedScrollHeight }}
|
className='dashboard-table'
|
||||||
rowKey='_id'
|
pagination={false}
|
||||||
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
scroll={{ y: adjustedScrollHeight }}
|
||||||
onScroll={handleScroll}
|
rowKey='_id'
|
||||||
onChange={handleTableChange}
|
loading={{
|
||||||
showSorterTooltip={false}
|
spinning: loading,
|
||||||
style={{ height: '100%' }}
|
indicator: <LoadingOutlined spin />
|
||||||
size={size}
|
}}
|
||||||
components={components}
|
onScroll={handleScroll}
|
||||||
onRow={onRow}
|
onChange={handleTableChange}
|
||||||
/>
|
showSorterTooltip={false}
|
||||||
)}
|
style={{ height: '100%' }}
|
||||||
|
size={size}
|
||||||
|
components={components}
|
||||||
|
onRow={onRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Splitter.Panel>
|
||||||
|
{showFilterSidebar && (
|
||||||
|
<Splitter.Panel>
|
||||||
|
<FilterSidebar
|
||||||
|
type={type}
|
||||||
|
filter={sidebarFilter}
|
||||||
|
onFilterChange={handleSidebarFilterChange}
|
||||||
|
masterFilter={masterFilter}
|
||||||
|
/>
|
||||||
|
</Splitter.Panel>
|
||||||
|
)}
|
||||||
|
</Splitter>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showFilterSidebar) {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
gap='middle'
|
|
||||||
align='flex-start'
|
|
||||||
style={{ width: '100%', height: '100%' }}
|
|
||||||
>
|
|
||||||
{tableContent}
|
|
||||||
<FilterSidebar
|
|
||||||
type={type}
|
|
||||||
filter={sidebarFilter}
|
|
||||||
onFilterChange={handleSidebarFilterChange}
|
|
||||||
masterFilter={masterFilter}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tableContent
|
return tableContent
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -291,6 +291,9 @@ const AuthProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
}, [authenticated, token, expiresAt, userProfile, persistSession])
|
}, [authenticated, token, expiresAt, userProfile, persistSession])
|
||||||
|
|
||||||
|
const profileImageDependency =
|
||||||
|
userProfile?.profileImage?._id ?? userProfile?.profileImage
|
||||||
|
|
||||||
// Fetch and cache profile image when userProfile.profileImage changes
|
// Fetch and cache profile image when userProfile.profileImage changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const profileImage = userProfile?.profileImage
|
const profileImage = userProfile?.profileImage
|
||||||
@ -365,7 +368,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
setProfileImageUrl(null)
|
setProfileImageUrl(null)
|
||||||
}
|
}
|
||||||
}, [userProfile?.profileImage?._id ?? userProfile?.profileImage, token])
|
}, [profileImageDependency, token])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('userProfile', userProfile)
|
console.log('userProfile', userProfile)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user