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 ( return (
<> <>
<Flex> <Flex style={{ minWidth: 0 }}>
{showLink ? ( {showLink ? (
<Link href={`mailto:${email}`} style={{ marginRight: 8 }}> <Link href={`mailto:${email}`} style={{ marginRight: 8 }}>
{email} {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, loading = false,
rollups = [], rollups = [],
showCard = true, showCard = true,
style = {},
...rest ...rest
}) => { }) => {
if (value && typeof value == 'function' && objectData) { if (value && typeof value == 'function' && objectData) {
@ -310,7 +311,7 @@ const ObjectProperty = ({
case 'text': case 'text':
if (value != null && value != '') { if (value != null && value != '') {
return ( return (
<Text ellipsis> <Text ellipsis style={style}>
{prefix} {prefix}
{value} {value}
{suffix} {suffix}
@ -888,7 +889,8 @@ ObjectProperty.propTypes = {
loading: PropTypes.bool, loading: PropTypes.bool,
rollups: PropTypes.arrayOf(PropTypes.object), rollups: PropTypes.arrayOf(PropTypes.object),
canAddRemove: PropTypes.bool, canAddRemove: PropTypes.bool,
showCard: PropTypes.bool showCard: PropTypes.bool,
style: PropTypes.object
} }
export default ObjectProperty export default ObjectProperty

View File

@ -11,10 +11,8 @@ import {
import { import {
Table, Table,
Skeleton, Skeleton,
Card,
Row, Row,
Col, Col,
Descriptions,
Flex, Flex,
Spin, Spin,
Button, Button,
@ -35,6 +33,7 @@ import {
getModelByName getModelByName
} from '../../../database/ObjectModels' } from '../../../database/ObjectModels'
import ObjectProperty from './ObjectProperty' import ObjectProperty from './ObjectProperty'
import ObjectCard from './ObjectCard'
import FilterSidebar from './FilterSidebar' import FilterSidebar from './FilterSidebar'
import XMarkIcon from '../../Icons/XMarkIcon' import XMarkIcon from '../../Icons/XMarkIcon'
import CheckIcon from '../../Icons/CheckIcon' import CheckIcon from '../../Icons/CheckIcon'
@ -128,7 +127,7 @@ const ObjectTable = forwardRef(
adjustedScrollHeight = 'calc(var(--unit-100vh) - 316px)' adjustedScrollHeight = 'calc(var(--unit-100vh) - 316px)'
} }
if (cards) { if (cards) {
adjustedScrollHeight = 'calc(var(--unit-100vh) - 280px)' adjustedScrollHeight = 'calc(var(--unit-100vh) - 210px)'
} }
if (isElectron) { if (isElectron) {
adjustedScrollHeight = 'calc(var(--unit-100vh) - 244px)' adjustedScrollHeight = 'calc(var(--unit-100vh) - 244px)'
@ -857,78 +856,25 @@ const ObjectTable = forwardRef(
> >
{tableData.map((record) => ( {tableData.map((record) => (
<Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}> <Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}>
<Card <div style={{ width: '100%', overflow: 'hidden' }}>
style={{ width: '100%', overflow: 'hidden' }}
styles={{ body: { padding: 0 } }}
loading={record.isSkeleton}
variant={'borderless'}
>
<RowForm <RowForm
record={record} record={record}
isEditing={isEditing} isEditing={isEditing}
onRegister={registerForm} onRegister={registerForm}
> >
<Flex align={'center'} vertical gap={'middle'}> <Flex align={'center'} vertical gap={'middle'}>
<Descriptions <ObjectCard
column={1} model={model}
size='small' modelProperties={modelProperties}
bordered={true} visibleColumns={visibleColumns}
style={{ width: '100%', tableLayout: 'fixed' }} record={record}
className='objectTableDescritions' isEditing={isEditing}
> rowActions={rowActions}
{(() => { renderActions={renderActions}
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>
</Flex> </Flex>
</RowForm> </RowForm>
</Card> </div>
</Col> </Col>
))} ))}
</Row> </Row>
@ -955,28 +901,29 @@ 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 }}>
<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 ? ( {cards ? (
<Spin indicator={<LoadingOutlined />} spinning={loading}> <Spin indicator={<LoadingOutlined />} spinning={loading}>
{renderCards()} {renderCards()}
</Spin> </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> </Flex>
) )