From 377c1b738a648250c968d4c85b4cfa608a1af2fd Mon Sep 17 00:00:00 2001 From: bellopushon Date: Fri, 26 Jun 2026 15:57:06 -0400 Subject: [PATCH] feat(automations): add deal pipeline selectors --- .../automations/automation-builder.tsx | 165 +++++++++++++++--- 1 file changed, 140 insertions(+), 25 deletions(-) diff --git a/src/components/automations/automation-builder.tsx b/src/components/automations/automation-builder.tsx index e2cba49..7632166 100644 --- a/src/components/automations/automation-builder.tsx +++ b/src/components/automations/automation-builder.tsx @@ -167,7 +167,7 @@ function blankConfig(type: AutomationStepType): Record { } // ------------------------------------------------------------ -// Account resources (tags, members, approved templates) +// Account resources (tags, members, approved templates, pipelines) // // Loaded once at the builder root and shared via context so the // tag / agent / template pickers below can offer existing resources @@ -181,6 +181,20 @@ interface AutomationResources { members: AccountMember[] templates: MessageTemplate[] customFields: CustomField[] + pipelines: PipelineOption[] + stages: PipelineStageOption[] +} + +interface PipelineOption { + id: string + name: string +} + +interface PipelineStageOption { + id: string + name: string + pipeline_id: string + position: number } const ResourcesContext = createContext({ @@ -188,6 +202,8 @@ const ResourcesContext = createContext({ members: [], templates: [], customFields: [], + pipelines: [], + stages: [], }) function useResources(): AutomationResources { @@ -199,6 +215,8 @@ function ResourcesProvider({ children }: { children: ReactNode }) { const [members, setMembers] = useState([]) const [templates, setTemplates] = useState([]) const [customFields, setCustomFields] = useState([]) + const [pipelines, setPipelines] = useState([]) + const [stages, setStages] = useState([]) useEffect(() => { let cancelled = false @@ -209,19 +227,27 @@ function ResourcesProvider({ children }: { children: ReactNode }) { // actually be sent (anything else 400s at send time), matching the // broadcast picker. void (async () => { - const [tagsRes, templatesRes, customFieldsRes] = await Promise.all([ - supabase.from("tags").select("*").order("name"), - supabase - .from("message_templates") - .select("*") - .eq("status", "APPROVED") - .order("name"), - supabase.from("custom_fields").select("*").order("field_name"), - ]) + const [tagsRes, templatesRes, customFieldsRes, pipelinesRes, stagesRes] = + await Promise.all([ + supabase.from("tags").select("*").order("name"), + supabase + .from("message_templates") + .select("*") + .eq("status", "APPROVED") + .order("name"), + supabase.from("custom_fields").select("*").order("field_name"), + supabase.from("pipelines").select("id, name").order("name"), + supabase + .from("pipeline_stages") + .select("id, name, pipeline_id, position") + .order("position"), + ]) if (cancelled) return setTags((tagsRes.data as TagRecord[] | null) ?? []) setTemplates((templatesRes.data as MessageTemplate[] | null) ?? []) setCustomFields((customFieldsRes.data as CustomField[] | null) ?? []) + setPipelines((pipelinesRes.data as PipelineOption[] | null) ?? []) + setStages((stagesRes.data as PipelineStageOption[] | null) ?? []) })() // Members go through the API so we inherit its email-visibility @@ -244,7 +270,9 @@ function ResourcesProvider({ children }: { children: ReactNode }) { }, []) return ( - + {children} ) @@ -382,6 +410,102 @@ function AgentSelect({ ) } +/** Pipeline + stage picker for Create Deal. The automation stores ids because + * the engine writes directly to deals, but authors should choose by name. */ +function DealPipelineFields({ + pipelineId, + stageId, + onChange, +}: { + pipelineId: string + stageId: string + onChange: (patch: { pipeline_id: string; stage_id: string }) => void +}) { + const { pipelines, stages } = useResources() + + if (pipelines.length === 0) { + return ( + <> + + + onChange({ pipeline_id: e.target.value, stage_id: stageId }) + } + className="bg-muted text-foreground" + /> + + + + onChange({ pipeline_id: pipelineId, stage_id: e.target.value }) + } + className="bg-muted text-foreground" + /> + + + ) + } + + const selectedPipeline = pipelines.find((p) => p.id === pipelineId) + const stageOptions = stages.filter((s) => s.pipeline_id === pipelineId) + const selectedStage = stageOptions.find((s) => s.id === stageId) + + return ( + <> + + + + + + + + ) +} + /** Template dropdown showing approved templates by name + language, * storing both template_name and language. Falls back to manual name + * language inputs when no approved templates are synced yet. */ @@ -1125,20 +1249,11 @@ function StepEditor({ case "create_deal": return ( <> - - set({ pipeline_id: e.target.value })} - className="bg-muted text-foreground" - /> - - - set({ stage_id: e.target.value })} - className="bg-muted text-foreground" - /> - + set(patch)} + />