103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import { useState, ReactNode } from "react";
|
|
import { Plus } from "lucide-react";
|
|
import type { LayoutElement } from "../api/viewService";
|
|
import RecordViewTable from "./RecordViewTable";
|
|
import DetailViewPanel from "./DetailViewPanel";
|
|
import FormModal from "./FormModal";
|
|
|
|
interface Props {
|
|
layout: LayoutElement[];
|
|
instanceId?: string;
|
|
onRefresh?: () => void;
|
|
onRowClick?: (instanceId: string) => void;
|
|
}
|
|
|
|
interface AnyNode {
|
|
type?: string;
|
|
style?: Record<string, string>;
|
|
config?: Record<string, any>;
|
|
children?: AnyNode[];
|
|
}
|
|
|
|
function flatten(nodes: AnyNode[] | undefined): AnyNode[] {
|
|
if (!nodes) return [];
|
|
const out: AnyNode[] = [];
|
|
for (const n of nodes) {
|
|
if (n.config && (n.config.type || n.config.job_template)) out.push(n);
|
|
if (n.children?.length) out.push(...flatten(n.children));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export default function ScreenRenderer({ layout, instanceId, onRefresh, onRowClick }: Props) {
|
|
const [formModal, setFormModal] = useState<{ activityId: string; instanceId?: string; title?: string } | null>(null);
|
|
|
|
const buttons: ReactNode[] = [];
|
|
const elements: { type: "rv" | "dv"; viewId: number | string; style?: Record<string, string> }[] = [];
|
|
|
|
for (const node of flatten(layout as AnyNode[])) {
|
|
const cfg = node.config!;
|
|
if (cfg.type === "button" || cfg.job_template) {
|
|
const aid =
|
|
cfg.params?.activity_uid_init ||
|
|
cfg.params?.activity_id_init ||
|
|
cfg.params?.activity_uid ||
|
|
cfg.params?.activity_id;
|
|
if (!aid) continue;
|
|
const label = (cfg.name || "").replace(/^\+\s*/, "");
|
|
buttons.push(
|
|
<button
|
|
key={aid}
|
|
onClick={() => setFormModal({ activityId: aid, instanceId: cfg.job_template === "perform_activity" ? instanceId : undefined, title: label })}
|
|
className="h-8 px-3.5 text-[12px] font-semibold text-white rounded-xl inline-flex items-center gap-1.5 transition-all hover:scale-[1.02] active:scale-[0.98] mh-glow"
|
|
style={{ background: "linear-gradient(135deg, #C8102E, #E84258)" }}
|
|
>
|
|
<Plus size={13} strokeWidth={2.5} />{label}
|
|
</button>
|
|
);
|
|
} else if (cfg.type === "recordsview") {
|
|
const id = cfg.view_uuid || cfg.view_id;
|
|
elements.push({ type: "rv", viewId: id, style: node.style });
|
|
} else if (cfg.type === "detailsview") {
|
|
const id = cfg.view_uuid || cfg.view_id;
|
|
elements.push({ type: "dv", viewId: id, style: node.style });
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="space-y-4">
|
|
{elements.map((el, i) => {
|
|
if (el.type === "rv") {
|
|
return (
|
|
<div key={i} style={el.style}>
|
|
<RecordViewTable
|
|
viewId={el.viewId}
|
|
onRowClick={onRowClick}
|
|
toolbarAction={buttons.length > 0 ? <div className="flex items-center gap-2">{buttons}</div> : undefined}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
if (el.type === "dv") {
|
|
return <div key={i} style={el.style}><DetailViewPanel viewId={el.viewId} instanceId={instanceId || ""} /></div>;
|
|
}
|
|
return null;
|
|
})}
|
|
{elements.length === 0 && buttons.length > 0 && (
|
|
<div className="flex items-center gap-2">{buttons}</div>
|
|
)}
|
|
</div>
|
|
{formModal && (
|
|
<FormModal
|
|
activityId={formModal.activityId}
|
|
instanceId={formModal.instanceId}
|
|
title={formModal.title}
|
|
onClose={() => setFormModal(null)}
|
|
onSuccess={() => { setFormModal(null); onRefresh?.(); }}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|