-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathHeaderHighlight.tsx
More file actions
105 lines (95 loc) · 4.34 KB
/
Copy pathHeaderHighlight.tsx
File metadata and controls
105 lines (95 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as React from 'react';
import { cn, Card, CardContent } from '@object-ui/components';
import type { HighlightField, FieldMetadata } from '@object-ui/types';
import { getCellRenderer } from '@object-ui/fields';
import { useSafeFieldLabel } from '@object-ui/react';
/** Renderer types that natively handle object values without crashing */
const OBJECT_SAFE_TYPES = ['lookup', 'master_detail', 'user', 'owner', 'file', 'image', 'object'];
export interface HeaderHighlightProps {
fields: HighlightField[];
data?: any;
className?: string;
/** Object name for i18n field label resolution */
objectName?: string;
/** Object schema for field metadata enrichment */
objectSchema?: any;
}
export const HeaderHighlight: React.FC<HeaderHighlightProps> = ({
fields,
data,
className,
objectName,
objectSchema,
}) => {
const { fieldLabel } = useSafeFieldLabel();
if (!fields.length || !data) return null;
// Filter to only fields with values
const visibleFields = fields.filter((f) => {
const val = data?.[f.name];
return val !== null && val !== undefined && val !== '';
});
if (visibleFields.length === 0) return null;
return (
<Card className={cn('bg-muted/30 border-dashed', className)}>
<CardContent className="py-3 px-4">
<div className={cn(
'grid gap-4',
visibleFields.length === 1 ? 'grid-cols-1' :
visibleFields.length === 2 ? 'grid-cols-2' :
visibleFields.length === 3 ? 'grid-cols-3' :
'grid-cols-2 md:grid-cols-4'
)}>
{visibleFields.map((field) => {
const value = data[field.name];
// Enrich field with objectSchema metadata for type-aware rendering
const objectDefField = objectSchema?.fields?.[field.name];
const resolvedType = field.type || objectDefField?.type;
const enrichedField: Record<string, any> = { ...field };
if (objectDefField) {
if (!field.type && objectDefField.type) enrichedField.type = objectDefField.type;
if (objectDefField.options && !enrichedField.options) enrichedField.options = objectDefField.options;
if (objectDefField.currency && !enrichedField.currency) enrichedField.currency = objectDefField.currency;
if (objectDefField.precision !== undefined && enrichedField.precision === undefined) enrichedField.precision = objectDefField.precision;
if (objectDefField.format && !enrichedField.format) enrichedField.format = objectDefField.format;
}
// Use type-aware cell renderer when field type is available
let displayValue: React.ReactNode = String(value);
if (resolvedType) {
const CellRenderer = getCellRenderer(resolvedType);
if (CellRenderer) {
// Guard: plain objects (e.g. MongoDB Decimal128 {$numberDecimal}, expanded refs)
// can crash renderers that pass non-primitive values straight to JSX children.
// Types like lookup/user/owner/file/image handle objects natively.
const isPlainObject = value !== null && typeof value === 'object'
&& !Array.isArray(value) && !(value instanceof Date);
if (isPlainObject && !OBJECT_SAFE_TYPES.includes(resolvedType)) {
displayValue = String(value?.name || value?.label || value?._id || JSON.stringify(value));
} else {
displayValue = <CellRenderer value={value} field={enrichedField as unknown as FieldMetadata} />;
}
}
}
return (
<div key={field.name} className="flex flex-col gap-0.5">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
{field.icon && <span className="mr-1">{field.icon}</span>}
{fieldLabel(objectName || '', field.name, field.label)}
</span>
<span className="text-sm font-semibold truncate">
{displayValue}
</span>
</div>
);
})}
</div>
</CardContent>
</Card>
);
};