153 lines
5.2 KiB
TypeScript
153 lines
5.2 KiB
TypeScript
import React, { useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { Box, Typography, Chip, Avatar, Dialog, DialogTitle, DialogContent, DialogActions, Button, Grid } from "@mui/material";
|
|
import type { FieldConfig } from "../../types";
|
|
import { applyDisplayFormat } from "./utils";
|
|
import { InlineRefField } from "./renderers/InlineRefField";
|
|
import { extractFields } from "../../transformers/field-config";
|
|
import { useAppContext } from "../../context/AppContext";
|
|
|
|
interface ListCellProps {
|
|
field: FieldConfig;
|
|
value: any;
|
|
displayFormat?: string;
|
|
basePath?: string;
|
|
}
|
|
|
|
export function ListCellRenderer({ field, value, displayFormat, basePath }: ListCellProps) {
|
|
const navigate = useNavigate();
|
|
const { schemas } = useAppContext();
|
|
const [inlineItem, setInlineItem] = useState<any>(null);
|
|
|
|
if (value === null || value === undefined) {
|
|
return <Typography variant="body2" color="text.disabled">—</Typography>;
|
|
}
|
|
|
|
const handleFkClick = (e: React.MouseEvent, fkValue: any) => {
|
|
e.stopPropagation();
|
|
if (!basePath || !field.fk || typeof fkValue !== "object") return;
|
|
const id = fkValue?.id;
|
|
if (id != null) {
|
|
navigate(`${basePath}/${field.fk.resource}/${id}`);
|
|
}
|
|
};
|
|
|
|
if (field.refSchema && !field.fk && !field.isArray && typeof value === "object") {
|
|
return <InlineRefField field={field} value={value} />;
|
|
}
|
|
|
|
const renderInlineItemFields = (itemValue: any) => {
|
|
const schema = field.refSchema ? schemas[field.refSchema] : undefined;
|
|
let fields: FieldConfig[] = [];
|
|
if (field.oneOfOptions && field.discriminatorProperty) {
|
|
const opt = field.oneOfOptions.find((o) => o.value === itemValue?.[field.discriminatorProperty!]);
|
|
if (opt) fields = opt.fields;
|
|
} else if (schema && field.refSchema) {
|
|
fields = extractFields(field.refSchema, schema, schemas);
|
|
}
|
|
return (
|
|
<Grid container spacing={2} sx={{ mt: 0.5 }}>
|
|
{fields.map((sf) => {
|
|
const fv = itemValue?.[sf.name];
|
|
return (
|
|
<Grid key={sf.name} item xs={12} sm={6}>
|
|
<Box sx={{ mb: 2 }}>
|
|
<Typography variant="caption" color="text.secondary" fontWeight={600} sx={{ mb: 0.25, display: "block" }}>
|
|
{sf.label}
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
{fv == null ? "—" : typeof fv === "object" ? (sf.inlineDisplayFormat ? applyDisplayFormat(fv, sf.inlineDisplayFormat) : JSON.stringify(fv)) : String(fv)}
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
);
|
|
})}
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
if (field.isArray && Array.isArray(value) && field.refSchema && !field.fk) {
|
|
if (value.length === 0) {
|
|
return <Typography variant="body2" color="text.disabled">—</Typography>;
|
|
}
|
|
return (
|
|
<Box sx={{ display: "flex", gap: 0.5, flexWrap: "wrap" }}>
|
|
{value.map((item: any, i: number) => {
|
|
const label = typeof item === "object"
|
|
? applyDisplayFormat(item, displayFormat ?? "")
|
|
: String(item);
|
|
return (
|
|
<Chip
|
|
key={i}
|
|
label={label}
|
|
size="small"
|
|
variant="outlined"
|
|
onClick={(e) => { e.stopPropagation(); setInlineItem(item); }}
|
|
sx={{ cursor: "pointer" }}
|
|
/>
|
|
);
|
|
})}
|
|
<Dialog open={!!inlineItem} onClose={() => setInlineItem(null)} maxWidth="sm" fullWidth>
|
|
<DialogTitle>{field.label}</DialogTitle>
|
|
<DialogContent dividers>
|
|
{inlineItem && renderInlineItemFields(inlineItem)}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setInlineItem(null)}>Close</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (field.fk && typeof value === "object" && !field.isArray) {
|
|
return (
|
|
<Chip
|
|
label={applyDisplayFormat(value, displayFormat ?? "")}
|
|
size="small"
|
|
variant="outlined"
|
|
onClick={(e) => handleFkClick(e, value)}
|
|
sx={basePath ? { cursor: "pointer" } : undefined}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (field.isArray && Array.isArray(value) && field.fk) {
|
|
return (
|
|
<Box sx={{ display: "flex", gap: 0.5, flexWrap: "wrap" }}>
|
|
{value.map((item: any, i: number) => {
|
|
const label = typeof item === "object" ? applyDisplayFormat(item, displayFormat ?? "") : String(item);
|
|
return (
|
|
<Chip
|
|
key={i}
|
|
label={label}
|
|
size="small"
|
|
variant="outlined"
|
|
onClick={(e) => handleFkClick(e, item)}
|
|
sx={basePath ? { cursor: "pointer" } : undefined}
|
|
/>
|
|
);
|
|
})}
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (field.enumValues) {
|
|
return <Chip label={value} size="small" />;
|
|
}
|
|
|
|
if (field.uiType === "image" && value) {
|
|
return <Avatar src={value} variant="rounded" sx={{ width: 40, height: 40 }} />;
|
|
}
|
|
|
|
if (field.type === "boolean") {
|
|
return <Chip label={value ? "Yes" : "No"} size="small" color={value ? "success" : "default"} />;
|
|
}
|
|
|
|
if (typeof value === "object") {
|
|
return <Typography variant="body2">{applyDisplayFormat(value, displayFormat ?? "")}</Typography>;
|
|
}
|
|
|
|
return <Typography variant="body2">{String(value)}</Typography>;
|
|
}
|