Improved card design for object table.

This commit is contained in:
Tom Butcher 2026-03-22 00:02:17 +00:00
parent 4f0fe89398
commit b4512a1948
4 changed files with 148 additions and 88 deletions

View File

@ -11,7 +11,7 @@ const EmailDisplay = ({ email, showCopy = true, showLink = false }) => {
return (
<>
<Flex>
<Flex style={{ minWidth: 0 }}>
{showLink ? (
<Link href={`mailto:${email}`} style={{ marginRight: 8 }}>
{email}

View File

@ -0,0 +1,111 @@
import { Descriptions, Card, Flex, Divider } from 'antd'
import PropTypes from 'prop-types'
import ObjectProperty from './ObjectProperty'
import { createElement } from 'react'
const ObjectCard = ({
model,
modelProperties,
visibleColumns = {},
record,
isEditing = false,
rowActions = [],
renderActions,
cardStyle = 'borderless'
}) => {
const descriptionItems = []
const modelIcon = createElement(model.icon, { style: { fontSize: 24 } })
model.columns.forEach((colName) => {
const prop = modelProperties.find((p) => p.name === colName)
if (prop) {
if (
(Object.keys(visibleColumns).length > 0 &&
visibleColumns[prop.name] === false) ||
prop.name == 'name'
) {
return
}
descriptionItems.push(
<Descriptions.Item label={prop.label} key={prop.name} colspan={2}>
<ObjectProperty
{...prop}
longId={false}
objectData={record}
isEditing={isEditing}
name={prop.name}
/>
</Descriptions.Item>
)
}
})
var actions = undefined
if (rowActions.length > 0) {
actions = renderActions(record)
}
return (
<Card
styles={{ body: { padding: 18 } }}
style={{ width: '100%' }}
variant={cardStyle}
>
<Flex vertical gap={8}>
{visibleColumns?.name == true && (
<Flex align='center' gap={12}>
{modelIcon}
<ObjectProperty
{...model.properties.find((p) => p.name === 'name')}
objectData={record}
isEditing={isEditing}
style={{
fontSize: 20,
fontWeight: '600',
lineHeight: 1,
overflow: 'visible'
}}
/>
{visibleColumns?.state == true && (
<ObjectProperty
{...model.properties.find((p) => p.name === 'state')}
objectData={record}
/>
)}
</Flex>
)}
<Descriptions
column={1}
size='small'
style={{ width: '100%', tableLayout: 'fixed' }}
className='objectTableDescritions'
>
{descriptionItems}
</Descriptions>
{actions && (
<>
<Divider style={{ margin: '2px 0 0 0' }} />
<Flex align='flex-end' gap={10}>
{actions}
</Flex>
</>
)}
</Flex>
</Card>
)
}
ObjectCard.propTypes = {
model: PropTypes.object.isRequired,
modelProperties: PropTypes.array.isRequired,
visibleColumns: PropTypes.object,
record: PropTypes.object.isRequired,
isEditing: PropTypes.bool,
rowActions: PropTypes.array,
renderActions: PropTypes.func.isRequired,
cardStyle: PropTypes.string
}
export default ObjectCard

View File

@ -92,6 +92,7 @@ const ObjectProperty = ({
loading = false,
rollups = [],
showCard = true,
style = {},
...rest
}) => {
if (value && typeof value == 'function' && objectData) {
@ -310,7 +311,7 @@ const ObjectProperty = ({
case 'text':
if (value != null && value != '') {
return (
<Text ellipsis>
<Text ellipsis style={style}>
{prefix}
{value}
{suffix}
@ -888,7 +889,8 @@ ObjectProperty.propTypes = {
loading: PropTypes.bool,
rollups: PropTypes.arrayOf(PropTypes.object),
canAddRemove: PropTypes.bool,
showCard: PropTypes.bool
showCard: PropTypes.bool,
style: PropTypes.object
}
export default ObjectProperty

View File

@ -11,10 +11,8 @@ import {
import {
Table,
Skeleton,
Card,
Row,
Col,
Descriptions,
Flex,
Spin,
Button,
@ -35,6 +33,7 @@ import {
getModelByName
} from '../../../database/ObjectModels'
import ObjectProperty from './ObjectProperty'
import ObjectCard from './ObjectCard'
import FilterSidebar from './FilterSidebar'
import XMarkIcon from '../../Icons/XMarkIcon'
import CheckIcon from '../../Icons/CheckIcon'
@ -128,7 +127,7 @@ const ObjectTable = forwardRef(
adjustedScrollHeight = 'calc(var(--unit-100vh) - 316px)'
}
if (cards) {
adjustedScrollHeight = 'calc(var(--unit-100vh) - 280px)'
adjustedScrollHeight = 'calc(var(--unit-100vh) - 210px)'
}
if (isElectron) {
adjustedScrollHeight = 'calc(var(--unit-100vh) - 244px)'
@ -857,78 +856,25 @@ const ObjectTable = forwardRef(
>
{tableData.map((record) => (
<Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}>
<Card
style={{ width: '100%', overflow: 'hidden' }}
styles={{ body: { padding: 0 } }}
loading={record.isSkeleton}
variant={'borderless'}
>
<div style={{ width: '100%', overflow: 'hidden' }}>
<RowForm
record={record}
isEditing={isEditing}
onRegister={registerForm}
>
<Flex align={'center'} vertical gap={'middle'}>
<Descriptions
column={1}
size='small'
bordered={true}
style={{ width: '100%', tableLayout: 'fixed' }}
className='objectTableDescritions'
>
{(() => {
const descriptionItems = []
// Add columns in the order specified by model.columns (same logic as table)
model.columns.forEach((colName) => {
const prop = modelProperties.find(
(p) => p.name === colName
)
if (prop) {
// Check if column should be visible based on visibleColumns prop
if (
Object.keys(visibleColumns).length > 0 &&
visibleColumns[prop.name] === false
) {
return // Skip this column if it's not visible
}
descriptionItems.push(
<Descriptions.Item
label={prop.label}
key={prop.name}
colspan={2}
>
<ObjectProperty
{...prop}
longId={false}
objectData={record}
isEditing={isEditing}
name={prop.name}
/>
</Descriptions.Item>
)
}
})
// Add actions if they exist (same as table)
if (rowActions.length > 0) {
descriptionItems.push(
<Descriptions.Item
label={'Actions'}
key={'actions'}
>
{renderActions(record)}
</Descriptions.Item>
)
}
return descriptionItems
})()}
</Descriptions>
<ObjectCard
model={model}
modelProperties={modelProperties}
visibleColumns={visibleColumns}
record={record}
isEditing={isEditing}
rowActions={rowActions}
renderActions={renderActions}
/>
</Flex>
</RowForm>
</Card>
</div>
</Col>
))}
</Row>
@ -955,28 +901,29 @@ const ObjectTable = forwardRef(
const tableContent = (
<Flex gap={'middle'} vertical style={{ flex: 1, minWidth: 0 }}>
<Table
ref={tableRef}
dataSource={tableData}
columns={columnsWithSkeleton}
className={cards ? 'dashboard-cards-header' : '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}
/>
{cards ? (
<Spin indicator={<LoadingOutlined />} spinning={loading}>
{renderCards()}
</Spin>
) : null}
) : (
<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}
/>
)}
</Flex>
)