summaryrefslogblamecommitdiff
path: root/makima/frontend/src/hooks/useDirectives.ts
blob: 898f6718f281ab8138d401ace0d99bad15aec37f (plain) (tree)




















                                                         
                   
                                  
                    










































                                                                              












                                                                          













                                                                            
                                          
                   


                       
              
                                                              
 
                                                                                            


                           


                                             

                              
                                                      
                                         
                                                                                                     
 



























































                                                                          
                                           
                    
                               


                    






                                                  





                                            




                                       
                        
                                 
             

    
import { useState, useEffect, useCallback } from "react";
import {
  type DirectiveSummary,
  type DirectiveWithSteps,
  type CreateDirectiveRequest,
  type UpdateDirectiveRequest,
  type CreateDirectiveStepRequest,
  listDirectives,
  createDirective,
  getDirective,
  updateDirective,
  deleteDirective,
  createDirectiveStep,
  deleteDirectiveStep,
  startDirective,
  pauseDirective,
  advanceDirective,
  completeDirectiveStep,
  failDirectiveStep,
  skipDirectiveStep,
  updateDirectiveGoal,
  cleanupDirective,
  pickUpOrders as pickUpOrdersApi,
  createDirectivePR,
} from "../lib/api";

export function useDirectives() {
  const [directives, setDirectives] = useState<DirectiveSummary[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const refresh = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const res = await listDirectives();
      setDirectives(res.directives);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to load directives");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    refresh();
  }, [refresh]);

  const create = useCallback(async (req: CreateDirectiveRequest) => {
    const d = await createDirective(req);
    await refresh();
    return d;
  }, [refresh]);

  const remove = useCallback(async (id: string) => {
    await deleteDirective(id);
    await refresh();
  }, [refresh]);

  return { directives, loading, error, refresh, create, remove };
}

export function useDirective(id: string | undefined) {
  const [directive, setDirective] = useState<DirectiveWithSteps | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // Silently refresh without setting loading state (for polls)
  const silentRefresh = useCallback(async () => {
    if (!id) return;
    try {
      const d = await getDirective(id);
      setDirective(d);
      setError(null);
    } catch (e) {
      // Don't overwrite existing data on poll failure
    }
  }, [id]);

  // Full refresh with loading state (for initial load / explicit refresh)
  const refresh = useCallback(async () => {
    if (!id) return;
    try {
      setLoading(true);
      setError(null);
      const d = await getDirective(id);
      setDirective(d);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to load directive");
    } finally {
      setLoading(false);
    }
  }, [id]);

  // Reset state and fetch when ID changes
  useEffect(() => {
    setDirective(null);
    setError(null);
    setLoading(true);
    refresh();
  }, [id]); // eslint-disable-line react-hooks/exhaustive-deps

  // Auto-poll while directive is active, has an orchestrator task, or has a completion task
  useEffect(() => {
    if (!directive) return;
    const needsPolling =
      directive.status === "active" ||
      directive.orchestratorTaskId != null ||
      directive.completionTaskId != null;
    if (!needsPolling) return;

    const interval = setInterval(silentRefresh, 5000);
    return () => clearInterval(interval);
  }, [directive?.status, directive?.orchestratorTaskId, directive?.completionTaskId, silentRefresh]);

  const update = useCallback(async (req: UpdateDirectiveRequest) => {
    if (!id) return;
    await updateDirective(id, req);
    await refresh();
  }, [id, refresh]);

  const addStep = useCallback(async (req: CreateDirectiveStepRequest) => {
    if (!id) return;
    await createDirectiveStep(id, req);
    await refresh();
  }, [id, refresh]);

  const removeStep = useCallback(async (stepId: string) => {
    if (!id) return;
    await deleteDirectiveStep(id, stepId);
    await refresh();
  }, [id, refresh]);

  const start = useCallback(async () => {
    if (!id) return;
    await startDirective(id);
    await refresh();
  }, [id, refresh]);

  const pause = useCallback(async () => {
    if (!id) return;
    await pauseDirective(id);
    await refresh();
  }, [id, refresh]);

  const advance = useCallback(async () => {
    if (!id) return;
    await advanceDirective(id);
    await refresh();
  }, [id, refresh]);

  const completeStep = useCallback(async (stepId: string) => {
    if (!id) return;
    await completeDirectiveStep(id, stepId);
    await refresh();
  }, [id, refresh]);

  const failStep = useCallback(async (stepId: string) => {
    if (!id) return;
    await failDirectiveStep(id, stepId);
    await refresh();
  }, [id, refresh]);

  const skipStep = useCallback(async (stepId: string) => {
    if (!id) return;
    await skipDirectiveStep(id, stepId);
    await refresh();
  }, [id, refresh]);

  const updateGoal = useCallback(async (goal: string) => {
    if (!id) return;
    await updateDirectiveGoal(id, goal);
    await refresh();
  }, [id, refresh]);

  const cleanup = useCallback(async () => {
    if (!id) return;
    await cleanupDirective(id);
    await refresh();
  }, [id, refresh]);

  const pickUpOrdersFn = useCallback(async () => {
    if (!id) return null;
    const result = await pickUpOrdersApi(id);
    await refresh();
    return result;
  }, [id, refresh]);

  const createPR = useCallback(async () => {
    if (!id) return;
    await createDirectivePR(id);
    await refresh();
  }, [id, refresh]);

  return {
    directive, loading, error, refresh,
    update, addStep, removeStep,
    start, pause, advance,
    completeStep, failStep, skipStep,
    updateGoal, cleanup,
    pickUpOrders: pickUpOrdersFn,
    createPR,
  };
}