|
|
|
|
@@ -1,10 +1,10 @@
|
|
|
|
|
import * as React from 'react';
|
|
|
|
|
import { alpha } from '@mui/material/styles';
|
|
|
|
|
import {
|
|
|
|
|
Box,
|
|
|
|
|
Typography,
|
|
|
|
|
Button,
|
|
|
|
|
IconButton,
|
|
|
|
|
Link,
|
|
|
|
|
Tooltip,
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
|
|
useMediaQuery,
|
|
|
|
|
useTheme,
|
|
|
|
|
Divider,
|
|
|
|
|
Chip,
|
|
|
|
|
Stack,
|
|
|
|
|
} from '@mui/material';
|
|
|
|
|
import {
|
|
|
|
|
DataGrid,
|
|
|
|
|
@@ -77,6 +79,7 @@ export default function EnhancedTable({
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (muiType === 'singleSelect' && field.options) {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
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) {
|
|
|
|
|
const value = params.value;
|
|
|
|
|
const isPk = fieldKey === config.primaryKey;
|
|
|
|
|
|
|
|
|
|
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 displayValue = typeof value === "object" ?
|
|
|
|
|
((field?.displayField && (value as Record<string, any>)[field.displayField]) || (value as any).id || (value as any)._id || (value as any).pk) : value;
|
|
|
|
|
const displayValue = getFormattedDisplayValue(value, field.displayField);
|
|
|
|
|
|
|
|
|
|
if (relationId) {
|
|
|
|
|
return (
|
|
|
|
|
<Link component="button" variant="body2" sx={{ fontWeight: 'inherit', textAlign: 'left' }} onClick={(e) => { e.stopPropagation(); onNavigate?.(field.relation!, String(relationId)); }}>
|
|
|
|
|
{displayValue}
|
|
|
|
|
</Link>
|
|
|
|
|
<Chip
|
|
|
|
|
label={displayValue}
|
|
|
|
|
size="small"
|
|
|
|
|
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)) {
|
|
|
|
|
const displayList = field.displayField ?
|
|
|
|
|
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(', ');
|
|
|
|
|
const tooltipTitle = value.map((item) => getFormattedDisplayValue(item, field.displayField)).join(', ');
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip title={tooltipTitle} arrow placement="top">
|
|
|
|
|
<Box component="span" sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }}>
|
|
|
|
|
{displayList}
|
|
|
|
|
</Box>
|
|
|
|
|
<Stack direction="row" spacing={0.5} sx={{ overflow: 'hidden', flexWrap: 'nowrap' }}>
|
|
|
|
|
{value.map((item, idx) => (
|
|
|
|
|
<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>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Simple Objects
|
|
|
|
|
if (field.type === 'object' && value) {
|
|
|
|
|
if (field.displayField && value[field.displayField]) return value[field.displayField];
|
|
|
|
|
return isMobile ? 'Object' : JSON.stringify(value);
|
|
|
|
|
return getFormattedDisplayValue(value, field.displayField) || (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 (isPk && !isMobile) {
|
|
|
|
|
return (
|
|
|
|
|
<Link component="button" variant="body2" sx={{ fontWeight: 'inherit' }} onClick={(e) => { e.stopPropagation(); navigate(`/${config.name}/${params.row[config.primaryKey]}`); }}>
|
|
|
|
|
{value}
|
|
|
|
|
</Link>
|
|
|
|
|
<Chip
|
|
|
|
|
label={value}
|
|
|
|
|
size="small"
|
|
|
|
|
color="primary"
|
|
|
|
|
onClick={(e) => { e.stopPropagation(); navigate(`/${config.name}/${params.row[config.primaryKey]}`); }}
|
|
|
|
|
sx={{ cursor: 'pointer', fontWeight: 'bold' }}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|