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