215 lines
5.3 KiB
JavaScript

import React, { forwardRef, useState } from 'react'
import { Typography, Space, Descriptions, Badge, Tag, Table } from 'antd'
import PropTypes from 'prop-types'
import IdText from './IdText'
import { AuditOutlined, LoadingOutlined } from '@ant-design/icons'
import TimeDisplay from '../common/TimeDisplay'
const { Text } = Typography
const formatPropertyName = (name) => {
return name
.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase())
}
const isObjectId = (value) => {
return typeof value === 'string' && /^[0-9a-fA-F]{24}$/.test(value)
}
const formatValue = (value, propertyName) => {
if (value === null || value === undefined || value === '') {
return <Text type='secondary'>n/a</Text>
}
// Handle colors specifically
if (propertyName === 'color') {
return <Badge color={value} text={value} />
}
if (propertyName === 'state' && typeof value === 'object' && value.type) {
return (
<Text>{value.type.charAt(0).toUpperCase() + value.type.slice(1)}</Text>
)
}
// Check if the value is a timestamp (ISO date string)
if (
typeof value === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
) {
return <TimeDisplay dateTime={value} />
}
if (typeof value === 'boolean' || value === true || value === false) {
return (
<Tag color={value ? 'success' : 'error'} style={{ margin: 0 }}>
{value ? 'Yes' : 'No'}
</Tag>
)
}
if (isObjectId(value)) {
return (
<IdText
id={value}
type={propertyName.toLowerCase().replaceAll('current', '')}
longId={false}
showHyperlink={true}
/>
)
}
if (typeof value === 'object') {
return <Text>{JSON.stringify(value)}</Text>
}
return <Text>{value}</Text>
}
const AuditLogTable = forwardRef(
({ items, loading, showTargetColumn, showOwnerColumn }, ref) => {
const [sortedInfo, setSortedInfo] = useState({
columnKey: 'createdAt',
order: 'descend'
})
const handleChange = (pagination, filters, sorter) => {
setSortedInfo(sorter)
}
const columns = [
{
title: '',
key: 'icon',
width: 50,
render: () => <AuditOutlined />
},
{
title: 'ID',
dataIndex: '_id',
key: 'id',
width: 180,
render: (text) => <IdText id={text} type={'auditlog'} longId={false} />,
sorter: (a, b) => a._id.localeCompare(b._id)
}
]
if (showOwnerColumn) {
columns.push(
{
title: 'Owner Name',
dataIndex: ['owner', 'name'],
key: 'name',
width: 200,
sorter: (a, b) =>
(a.owner?.name || '').localeCompare(b.owner?.name || '')
},
{
title: 'Owner',
key: 'owner',
width: 180,
render: (record) => (
<IdText
id={record.owner._id}
type={record.ownerModel.toLowerCase()}
longId={false}
showHyperlink={true}
/>
)
}
)
}
if (showTargetColumn) {
columns.push({
title: 'Target',
key: 'target',
width: 180,
render: (record) => (
<IdText
id={record.target}
type={record.targetModel.toLowerCase()}
longId={false}
showHyperlink={true}
/>
)
})
}
columns.push({
title: 'Properties',
dataIndex: 'type',
key: 'type',
width: 550,
render: (_, record) => {
const oldValue = record.oldValue || {}
const newValue = record.newValue || {}
return (
<Descriptions size='small' column={1}>
{Object.keys(newValue).map((key) => (
<Descriptions.Item key={key} label={formatPropertyName(key)}>
<Space>
{formatValue(oldValue[key], key)}
<Text type='secondary'></Text>
{formatValue(newValue[key], key)}
</Space>
</Descriptions.Item>
))}
</Descriptions>
)
}
})
columns.push({
title: 'Timestamp',
dataIndex: 'createdAt',
key: 'createdAt',
width: 180,
defaultSortOrder: 'descend',
sorter: (a, b) => new Date(a.createdAt) - new Date(b.createdAt),
render: (createdAt) => {
if (createdAt) {
return <TimeDisplay dateTime={createdAt} />
} else {
return 'n/a'
}
}
})
return (
<Table
ref={ref}
columns={columns}
dataSource={items}
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
pagination={false}
scroll={{ x: 'max-content' }}
onChange={handleChange}
sortDirections={['ascend', 'descend']}
sortOrder={
sortedInfo.columnKey === 'createdAt' ? sortedInfo.order : null
}
/>
)
}
)
AuditLogTable.displayName = 'AuditLogTable'
AuditLogTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
loading: PropTypes.bool,
showTargetColumn: PropTypes.bool,
showOwnerColumn: PropTypes.bool
}
AuditLogTable.defaultProps = {
loading: false,
showTargetColumn: true,
showOwnerColumn: true
}
export default AuditLogTable