From dc2e281347867feab71f0726d3217be67d2934e6 Mon Sep 17 00:00:00 2001 From: Yashas Date: Wed, 29 Jul 2026 12:51:27 +0530 Subject: [PATCH] render the workflow-launch buttons configured on rv_screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Create New Invoice" button was configured all along, on the rv_screen layout rather than the screen layout, and was being dropped twice over: RecordViewTable fetched the rv_screen but kept only rv_template_uid and threw away `layout`, and ScreenRenderer matched only the legacy params.activity_id_init while current studio writes activity_uid_init. collectStartButtons deep-walks either shape — the button nested under a node's `config` as screen layouts do, or carrying type/params/job_template at its own top level as rv_screens do — and accepts activity_uid_init, activity_id_init, activity_uid and activity_id, newest key first. RecordViewTable renders any start_state_machine buttons from its rv_screen in the toolbar alongside the injected toolbarAction; ScreenRenderer routes through the same helper and now skips buttons with no resolvable activity instead of rendering a dead control. Verified against app 169's live rv_screen config: yields exactly one button, "Create New Invoice" -> Submit Invoice, ignoring the show_detailview_popup entry. Co-Authored-By: Claude Opus 5 (1M context) --- src/components/RecordViewTable.tsx | 29 ++++++++-- src/components/ScreenRenderer.tsx | 7 ++- src/lib/startButtons.ts | 86 ++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 src/lib/startButtons.ts 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(