summaryrefslogtreecommitdiff
path: root/makima/frontend/src/hooks/useDirectives.ts
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/hooks/useDirectives.ts')
-rw-r--r--makima/frontend/src/hooks/useDirectives.ts298
1 files changed, 298 insertions, 0 deletions
diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts
new file mode 100644
index 0000000..6e1654f
--- /dev/null
+++ b/makima/frontend/src/hooks/useDirectives.ts
@@ -0,0 +1,298 @@
+import { useState, useCallback, useEffect, useRef } from "react";
+import {
+ listDirectives,
+ getDirective,
+ createDirective,
+ updateDirective,
+ archiveDirective,
+ startDirective,
+ pauseDirective,
+ resumeDirective,
+ stopDirective,
+ getDirectiveGraph,
+ subscribeToDirectiveEvents,
+ type DirectiveSummary,
+ type DirectiveWithProgress,
+ type DirectiveGraphResponse,
+ type DirectiveStatus,
+ type DirectiveEvent,
+ type CreateDirectiveRequest,
+ type UpdateDirectiveRequest,
+ type StartDirectiveResponse,
+} from "../lib/api";
+
+interface UseDirectivesResult {
+ directives: DirectiveSummary[];
+ loading: boolean;
+ error: string | null;
+ refresh: () => Promise<void>;
+ createNewDirective: (req: CreateDirectiveRequest) => Promise<DirectiveWithProgress | null>;
+ updateExistingDirective: (
+ directiveId: string,
+ req: UpdateDirectiveRequest
+ ) => Promise<DirectiveWithProgress | null>;
+ archiveExistingDirective: (directiveId: string) => Promise<boolean>;
+ getDirectiveById: (directiveId: string) => Promise<DirectiveWithProgress | null>;
+ getGraph: (directiveId: string) => Promise<DirectiveGraphResponse | null>;
+ start: (directiveId: string) => Promise<StartDirectiveResponse | null>;
+ pause: (directiveId: string) => Promise<boolean>;
+ resume: (directiveId: string) => Promise<boolean>;
+ stop: (directiveId: string) => Promise<boolean>;
+}
+
+export function useDirectives(statusFilter?: DirectiveStatus): UseDirectivesResult {
+ const [directives, setDirectives] = useState<DirectiveSummary[]>([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+
+ const fetchDirectives = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const response = await listDirectives(statusFilter);
+ setDirectives(response.directives);
+ } catch (err) {
+ console.error("Failed to fetch directives:", err);
+ setError(err instanceof Error ? err.message : "Failed to fetch directives");
+ } finally {
+ setLoading(false);
+ }
+ }, [statusFilter]);
+
+ useEffect(() => {
+ fetchDirectives();
+ }, [fetchDirectives]);
+
+ const createNewDirective = useCallback(
+ async (req: CreateDirectiveRequest): Promise<DirectiveWithProgress | null> => {
+ try {
+ const directive = await createDirective(req);
+ // Refresh the list
+ await fetchDirectives();
+ // Return the full directive with progress
+ return await getDirective(directive.id);
+ } catch (err) {
+ console.error("Failed to create directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to create directive");
+ return null;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ const updateExistingDirective = useCallback(
+ async (
+ directiveId: string,
+ req: UpdateDirectiveRequest
+ ): Promise<DirectiveWithProgress | null> => {
+ try {
+ await updateDirective(directiveId, req);
+ // Refresh the list
+ await fetchDirectives();
+ // Return the updated directive
+ return await getDirective(directiveId);
+ } catch (err) {
+ console.error("Failed to update directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to update directive");
+ return null;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ const archiveExistingDirective = useCallback(
+ async (directiveId: string): Promise<boolean> => {
+ try {
+ await archiveDirective(directiveId);
+ // Refresh the list
+ await fetchDirectives();
+ return true;
+ } catch (err) {
+ console.error("Failed to archive directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to archive directive");
+ return false;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ const getDirectiveById = useCallback(
+ async (directiveId: string): Promise<DirectiveWithProgress | null> => {
+ try {
+ return await getDirective(directiveId);
+ } catch (err) {
+ console.error("Failed to get directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to get directive");
+ return null;
+ }
+ },
+ []
+ );
+
+ const getGraph = useCallback(
+ async (directiveId: string): Promise<DirectiveGraphResponse | null> => {
+ try {
+ return await getDirectiveGraph(directiveId);
+ } catch (err) {
+ console.error("Failed to get directive graph:", err);
+ setError(err instanceof Error ? err.message : "Failed to get directive graph");
+ return null;
+ }
+ },
+ []
+ );
+
+ const start = useCallback(
+ async (directiveId: string): Promise<StartDirectiveResponse | null> => {
+ try {
+ const response = await startDirective(directiveId);
+ await fetchDirectives();
+ return response;
+ } catch (err) {
+ console.error("Failed to start directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to start directive");
+ return null;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ const pause = useCallback(
+ async (directiveId: string): Promise<boolean> => {
+ try {
+ await pauseDirective(directiveId);
+ await fetchDirectives();
+ return true;
+ } catch (err) {
+ console.error("Failed to pause directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to pause directive");
+ return false;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ const resume = useCallback(
+ async (directiveId: string): Promise<boolean> => {
+ try {
+ await resumeDirective(directiveId);
+ await fetchDirectives();
+ return true;
+ } catch (err) {
+ console.error("Failed to resume directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to resume directive");
+ return false;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ const stop = useCallback(
+ async (directiveId: string): Promise<boolean> => {
+ try {
+ await stopDirective(directiveId);
+ await fetchDirectives();
+ return true;
+ } catch (err) {
+ console.error("Failed to stop directive:", err);
+ setError(err instanceof Error ? err.message : "Failed to stop directive");
+ return false;
+ }
+ },
+ [fetchDirectives]
+ );
+
+ return {
+ directives,
+ loading,
+ error,
+ refresh: fetchDirectives,
+ createNewDirective,
+ updateExistingDirective,
+ archiveExistingDirective,
+ getDirectiveById,
+ getGraph,
+ start,
+ pause,
+ resume,
+ stop,
+ };
+}
+
+/** Hook for subscribing to real-time directive events via SSE */
+export function useDirectiveEventSubscription(
+ directiveId: string | null,
+ onEvent?: (event: DirectiveEvent) => void
+): {
+ events: DirectiveEvent[];
+ isConnected: boolean;
+ error: string | null;
+} {
+ const [events, setEvents] = useState<DirectiveEvent[]>([]);
+ const [isConnected, setIsConnected] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+ const cleanupRef = useRef<(() => void) | null>(null);
+
+ useEffect(() => {
+ // Clean up any existing subscription
+ if (cleanupRef.current) {
+ cleanupRef.current();
+ cleanupRef.current = null;
+ }
+
+ if (!directiveId) {
+ setIsConnected(false);
+ setEvents([]);
+ return;
+ }
+
+ // Subscribe to events
+ let mounted = true;
+
+ const setupSubscription = async () => {
+ try {
+ const cleanup = await subscribeToDirectiveEvents(
+ directiveId,
+ (event) => {
+ if (mounted) {
+ setEvents((prev) => [...prev, event]);
+ onEvent?.(event);
+ }
+ },
+ (err) => {
+ if (mounted) {
+ setError(err.message);
+ setIsConnected(false);
+ }
+ }
+ );
+
+ if (mounted) {
+ cleanupRef.current = cleanup;
+ setIsConnected(true);
+ setError(null);
+ } else {
+ // Component unmounted during setup, clean up immediately
+ cleanup();
+ }
+ } catch (err) {
+ if (mounted) {
+ setError(err instanceof Error ? err.message : "Failed to subscribe to events");
+ setIsConnected(false);
+ }
+ }
+ };
+
+ setupSubscription();
+
+ return () => {
+ mounted = false;
+ if (cleanupRef.current) {
+ cleanupRef.current();
+ cleanupRef.current = null;
+ }
+ };
+ }, [directiveId, onEvent]);
+
+ return { events, isConnected, error };
+}