ui improvements for data ttable
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { alpha } from '@mui/material/styles';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
Link,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
Divider,
|
Divider,
|
||||||
|
Chip,
|
||||||
|
Stack,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
DataGrid,
|
DataGrid,
|
||||||
@@ -77,6 +79,7 @@ export default function EnhancedTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (muiType === 'singleSelect' && field.options) {
|
if (muiType === 'singleSelect' && field.options) {
|
||||||
|
// @ts-ignore
|
||||||
col.valueOptions = field.options;
|
col.valueOptions = field.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,58 +227,120 @@ function MobileCardRow({ row, config, onDelete, onNavigate, navigate }: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFormattedDisplayValue(item: any, displayField?: string | string[]) {
|
||||||
|
if (!item) return "";
|
||||||
|
if (!displayField) return item.name || item.title || item.label || item.id || JSON.stringify(item);
|
||||||
|
|
||||||
|
if (Array.isArray(displayField)) {
|
||||||
|
return displayField
|
||||||
|
.map(key => item[key])
|
||||||
|
.filter(val => val !== undefined && val !== null)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return item[displayField] || item.id || JSON.stringify(item);
|
||||||
|
}
|
||||||
|
|
||||||
function FieldRenderer({ params, field, fieldKey, config, onNavigate, navigate, isMobile }: any) {
|
function FieldRenderer({ params, field, fieldKey, config, onNavigate, navigate, isMobile }: any) {
|
||||||
const value = params.value;
|
const value = params.value;
|
||||||
const isPk = fieldKey === config.primaryKey;
|
const isPk = fieldKey === config.primaryKey;
|
||||||
|
|
||||||
if (field.formatter) return field.formatter(value);
|
if (field.formatter) return field.formatter(value);
|
||||||
|
|
||||||
if (field.relation && value) {
|
// 1. Single Relation
|
||||||
|
if (field.relation && value && !Array.isArray(value)) {
|
||||||
const relationId = typeof value === 'object' ? (value.id || value._id || value.pk) : value;
|
const relationId = typeof value === 'object' ? (value.id || value._id || value.pk) : value;
|
||||||
const displayValue = typeof value === "object" ?
|
const displayValue = getFormattedDisplayValue(value, field.displayField);
|
||||||
((field?.displayField && (value as Record<string, any>)[field.displayField]) || (value as any).id || (value as any)._id || (value as any).pk) : value;
|
|
||||||
|
|
||||||
if (relationId) {
|
return (
|
||||||
return (
|
<Chip
|
||||||
<Link component="button" variant="body2" sx={{ fontWeight: 'inherit', textAlign: 'left' }} onClick={(e) => { e.stopPropagation(); onNavigate?.(field.relation!, String(relationId)); }}>
|
label={displayValue}
|
||||||
{displayValue}
|
size="small"
|
||||||
</Link>
|
variant="outlined"
|
||||||
);
|
color="primary"
|
||||||
}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (relationId) onNavigate?.(field.relation!, String(relationId));
|
||||||
|
}}
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Multi-Select (Array of relations or simple strings)
|
||||||
if (field.type === 'array' && Array.isArray(value)) {
|
if (field.type === 'array' && Array.isArray(value)) {
|
||||||
const displayList = field.displayField ?
|
const tooltipTitle = value.map((item) => getFormattedDisplayValue(item, field.displayField)).join(', ');
|
||||||
value.map((item) => (typeof item === 'object' ? item[field.displayField!] : item)).filter(Boolean).join(', ') :
|
|
||||||
`${value.length} items`;
|
|
||||||
|
|
||||||
const tooltipTitle = value.map((item) => {
|
|
||||||
if (typeof item !== 'object') return String(item);
|
|
||||||
return item.name || item.title || item.label || item[field.displayField!] || JSON.stringify(item);
|
|
||||||
}).join(', ');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltipTitle} arrow placement="top">
|
<Tooltip title={tooltipTitle} arrow placement="top">
|
||||||
<Box component="span" sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }}>
|
<Stack direction="row" spacing={0.5} sx={{ overflow: 'hidden', flexWrap: 'nowrap' }}>
|
||||||
{displayList}
|
{value.map((item, idx) => (
|
||||||
</Box>
|
<Chip
|
||||||
|
key={idx}
|
||||||
|
label={getFormattedDisplayValue(item, field.displayField)}
|
||||||
|
size="small"
|
||||||
|
variant="filled"
|
||||||
|
sx={{ maxWidth: 120 }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (field.relation) {
|
||||||
|
const id = typeof item === 'object' ? (item.id || item._id) : item;
|
||||||
|
if (id) onNavigate?.(field.relation!, String(id));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Simple Objects
|
||||||
if (field.type === 'object' && value) {
|
if (field.type === 'object' && value) {
|
||||||
if (field.displayField && value[field.displayField]) return value[field.displayField];
|
return getFormattedDisplayValue(value, field.displayField) || (isMobile ? 'Object' : JSON.stringify(value));
|
||||||
return isMobile ? 'Object' : JSON.stringify(value);
|
}
|
||||||
|
|
||||||
|
if (field.type === 'number' && typeof value === 'number') {
|
||||||
|
const isNegative = value < 0;
|
||||||
|
const color = isNegative ? 'error' : 'success';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
label={value.toLocaleString()}
|
||||||
|
size="small"
|
||||||
|
color={color}
|
||||||
|
variant="filled"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
minWidth: 60,
|
||||||
|
// Soft background with bold text for a premium feel
|
||||||
|
bgcolor: (theme) => alpha(theme.palette[color].main, 0.15),
|
||||||
|
color: (theme) => theme.palette[color].dark,
|
||||||
|
'& .MuiChip-label': { px: 1.5 }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'boolean') {
|
||||||
|
return value ? (
|
||||||
|
<Chip label="Yes" size="small" color="success" variant="outlined" sx={{ height: 20, fontSize: '0.7rem' }} />
|
||||||
|
) : (
|
||||||
|
<Chip label="No" size="small" color="default" variant="outlined" sx={{ height: 20, fontSize: '0.7rem' }} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'boolean') return value ? 'Yes' : 'No';
|
|
||||||
if (field.type === 'datetime' || field.type === 'date') return value ? new Date(value).toLocaleString() : '';
|
if (field.type === 'datetime' || field.type === 'date') return value ? new Date(value).toLocaleString() : '';
|
||||||
|
|
||||||
if (isPk && !isMobile) {
|
if (isPk && !isMobile) {
|
||||||
return (
|
return (
|
||||||
<Link component="button" variant="body2" sx={{ fontWeight: 'inherit' }} onClick={(e) => { e.stopPropagation(); navigate(`/${config.name}/${params.row[config.primaryKey]}`); }}>
|
<Chip
|
||||||
{value}
|
label={value}
|
||||||
</Link>
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
onClick={(e) => { e.stopPropagation(); navigate(`/${config.name}/${params.row[config.primaryKey]}`); }}
|
||||||
|
sx={{ cursor: 'pointer', fontWeight: 'bold' }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const configuration: Record<string, ResourceOverride> = {
|
|||||||
displayField: "name",
|
displayField: "name",
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
displayField: "icon",
|
displayField: ["name", "icon"],
|
||||||
},
|
},
|
||||||
occurred_at: {
|
occurred_at: {
|
||||||
formatter: (val: string) => {
|
formatter: (val: string) => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export interface ResourceField {
|
|||||||
options?: string[];
|
options?: string[];
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
schema?: Record<string, ResourceField>;
|
schema?: Record<string, ResourceField>;
|
||||||
displayField?: string;
|
displayField?: string | string[];
|
||||||
formatter?: (value: any) => string;
|
formatter?: (value: any) => string;
|
||||||
relation?: string; // Name of the target resource
|
relation?: string; // Name of the target resource
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface FieldOverride {
|
export interface FieldOverride {
|
||||||
displayField?: string;
|
displayField?: string | string[];
|
||||||
display?: boolean;
|
display?: boolean;
|
||||||
formatter?: (value: any) => string;
|
formatter?: (value: any) => string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user