diff --git a/src/components/RecordViewTable.tsx b/src/components/RecordViewTable.tsx index df526ed..71369af 100644 --- a/src/components/RecordViewTable.tsx +++ b/src/components/RecordViewTable.tsx @@ -3,12 +3,13 @@ import { useNavigate } from "react-router-dom"; import { ChevronLeft, ChevronRight, Search, ArrowUp, ArrowDown, X, CheckCircle2, XCircle, PauseCircle, MessageSquare, - Inbox, MoreHorizontal, ArrowRight, RefreshCw, + Inbox, MoreHorizontal, ArrowRight, RefreshCw, Plus, } from "lucide-react"; import { getRVScreen, getRecordView, type RecordViewField, type SearchQuery } from "../api/viewService"; import { TableSkeleton } from "./Skeleton"; import FormModal from "./FormModal"; import { STATE_IDS, ACTIVITY_IDS } from "../config"; +import { startWorkflowButtons, type StartButton } from "../lib/startButtons"; // --------------------------------------------------------------------------- // Row actions by state @@ -46,13 +47,21 @@ export default function RecordViewTable({ viewId, onRowClick, toolbarAction }: P const [loading, setLoading] = useState(true); const [dataLoading, setDataLoading] = useState(false); const [error, setError] = useState(null); - const [formModal, setFormModal] = useState<{ activityId: string; instanceId: string; title: string } | null>(null); + // instanceId is absent for start-workflow buttons (they create the instance). + const [formModal, setFormModal] = useState<{ activityId: string; instanceId?: string; title: string } | null>(null); + // Workflow-launching buttons configured on the rv_screen ("Create New + // Invoice" and friends). They live in the rv_screen layout, not the screen + // layout, so this is the only place they can be picked up. + const [startButtons, setStartButtons] = useState([]); const searchRef = useRef(null); useEffect(() => { setLoading(true); getRVScreen(viewId) - .then((rv) => setRvTemplateId(rv.rv_template_uid)) + .then((rv) => { + setRvTemplateId(rv.rv_template_uid); + setStartButtons(startWorkflowButtons(rv.layout)); + }) .catch((err) => { setError(err.message); setLoading(false); }); }, [viewId]); @@ -145,8 +154,18 @@ export default function RecordViewTable({ viewId, onRowClick, toolbarAction }: P )} - {toolbarAction && ( -
+ {(startButtons.length > 0 || toolbarAction) && ( +
+ {startButtons.map((b) => ( + + ))} {toolbarAction}
)} diff --git a/src/components/ScreenRenderer.tsx b/src/components/ScreenRenderer.tsx index 40893b8..42f6dac 100644 --- a/src/components/ScreenRenderer.tsx +++ b/src/components/ScreenRenderer.tsx @@ -4,6 +4,7 @@ import type { LayoutElement } from "../api/viewService"; import RecordViewTable from "./RecordViewTable"; import DetailViewPanel from "./DetailViewPanel"; import FormModal from "./FormModal"; +import { collectStartButtons } from "../lib/startButtons"; interface Props { layout: LayoutElement[]; instanceId?: string; onRefresh?: () => void; onRowClick?: (instanceId: string) => void; } @@ -25,7 +26,11 @@ export default function ScreenRenderer({ layout, instanceId, onRefresh, onRowCli const visit = (el: any) => { const cfg = el.config || {}; if (cfg.type === "button" || cfg.job_template) { - const aid = cfg.params?.activity_id_init || cfg.params?.activity_id; + // Current studio writes params.activity_uid_init; older configs used + // activity_id_init. collectStartButtons accepts both — see lib/startButtons. + const [btn] = collectStartButtons(cfg); + const aid = btn?.activityId; + if (!aid) return; // a button with no activity to run is not renderable const label = (cfg.name || "").replace(/^\+\s*/, ""); buttons.push(