summaryrefslogblamecommitdiff
path: root/makima/frontend/src/hooks/useContracts.ts
blob: f80352733e789f8c1e697808f09bf813273791e1 (plain) (tree)



















































































































































































































































































































                                                                                
import { useState, useCallback, useEffect } from "react";
import {
  listContracts,
  getContract,
  createContract,
  updateContract,
  deleteContract,
  changeContractPhase,
  getContractEvents,
  addRemoteRepository,
  addLocalRepository,
  createManagedRepository,
  deleteContractRepository,
  setRepositoryPrimary,
  addTaskToContract,
  removeTaskFromContract,
  VersionConflictError,
  type ContractSummary,
  type ContractWithRelations,
  type ContractEvent,
  type ContractRepository,
  type ContractPhase,
  type CreateContractRequest,
  type UpdateContractRequest,
  type AddRemoteRepositoryRequest,
  type AddLocalRepositoryRequest,
  type CreateManagedRepositoryRequest,
} from "../lib/api";

export interface ConflictState {
  hasConflict: boolean;
  expectedVersion: number;
  actualVersion: number;
}

export function useContracts() {
  const [contracts, setContracts] = useState<ContractSummary[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [conflict, setConflict] = useState<ConflictState | null>(null);

  const fetchContracts = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const response = await listContracts();
      setContracts(response.contracts);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to fetch contracts");
    } finally {
      setLoading(false);
    }
  }, []);

  const fetchContract = useCallback(
    async (id: string): Promise<ContractWithRelations | null> => {
      setError(null);
      try {
        return await getContract(id);
      } catch (e) {
        setError(e instanceof Error ? e.message : "Failed to fetch contract");
        return null;
      }
    },
    []
  );

  const saveContract = useCallback(
    async (data: CreateContractRequest): Promise<ContractSummary | null> => {
      setError(null);
      try {
        const contract = await createContract(data);
        await fetchContracts(); // Refresh list
        return contract;
      } catch (e) {
        setError(e instanceof Error ? e.message : "Failed to save contract");
        return null;
      }
    },
    [fetchContracts]
  );

  const editContract = useCallback(
    async (
      id: string,
      data: UpdateContractRequest
    ): Promise<ContractSummary | null> => {
      setError(null);
      setConflict(null);
      try {
        const contract = await updateContract(id, data);
        await fetchContracts(); // Refresh list
        return contract;
      } catch (e) {
        if (e instanceof VersionConflictError) {
          setConflict({
            hasConflict: true,
            expectedVersion: e.expectedVersion,
            actualVersion: e.actualVersion,
          });
          return null;
        }
        setError(e instanceof Error ? e.message : "Failed to update contract");
        return null;
      }
    },
    [fetchContracts]
  );

  const clearConflict = useCallback(() => {
    setConflict(null);
  }, []);

  const removeContract = useCallback(
    async (id: string): Promise<boolean> => {
      setError(null);
      try {
        await deleteContract(id);
        await fetchContracts(); // Refresh list
        return true;
      } catch (e) {
        setError(e instanceof Error ? e.message : "Failed to delete contract");
        return false;
      }
    },
    [fetchContracts]
  );

  const changePhase = useCallback(
    async (
      id: string,
      phase: ContractPhase
    ): Promise<ContractSummary | null> => {
      setError(null);
      try {
        const contract = await changeContractPhase(id, phase);
        await fetchContracts(); // Refresh list
        return contract;
      } catch (e) {
        setError(e instanceof Error ? e.message : "Failed to change phase");
        return null;
      }
    },
    [fetchContracts]
  );

  const fetchEvents = useCallback(
    async (id: string): Promise<ContractEvent[]> => {
      setError(null);
      try {
        return await getContractEvents(id);
      } catch (e) {
        setError(e instanceof Error ? e.message : "Failed to fetch events");
        return [];
      }
    },
    []
  );

  // Repository management
  const addRemoteRepo = useCallback(
    async (
      contractId: string,
      data: AddRemoteRepositoryRequest
    ): Promise<ContractRepository | null> => {
      setError(null);
      try {
        return await addRemoteRepository(contractId, data);
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to add remote repository"
        );
        return null;
      }
    },
    []
  );

  const addLocalRepo = useCallback(
    async (
      contractId: string,
      data: AddLocalRepositoryRequest
    ): Promise<ContractRepository | null> => {
      setError(null);
      try {
        return await addLocalRepository(contractId, data);
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to add local repository"
        );
        return null;
      }
    },
    []
  );

  const createManagedRepo = useCallback(
    async (
      contractId: string,
      data: CreateManagedRepositoryRequest
    ): Promise<ContractRepository | null> => {
      setError(null);
      try {
        return await createManagedRepository(contractId, data);
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to create managed repository"
        );
        return null;
      }
    },
    []
  );

  const removeRepo = useCallback(
    async (contractId: string, repoId: string): Promise<boolean> => {
      setError(null);
      try {
        await deleteContractRepository(contractId, repoId);
        return true;
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to delete repository"
        );
        return false;
      }
    },
    []
  );

  const setRepoPrimary = useCallback(
    async (contractId: string, repoId: string): Promise<boolean> => {
      setError(null);
      try {
        await setRepositoryPrimary(contractId, repoId);
        return true;
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to set repository as primary"
        );
        return false;
      }
    },
    []
  );

  // Task association
  const addTask = useCallback(
    async (contractId: string, taskId: string): Promise<boolean> => {
      setError(null);
      try {
        await addTaskToContract(contractId, taskId);
        return true;
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to add task to contract"
        );
        return false;
      }
    },
    []
  );

  const removeTask = useCallback(
    async (contractId: string, taskId: string): Promise<boolean> => {
      setError(null);
      try {
        await removeTaskFromContract(contractId, taskId);
        return true;
      } catch (e) {
        setError(
          e instanceof Error ? e.message : "Failed to remove task from contract"
        );
        return false;
      }
    },
    []
  );

  // Initial fetch
  useEffect(() => {
    fetchContracts();
  }, [fetchContracts]);

  return {
    contracts,
    loading,
    error,
    conflict,
    clearConflict,
    fetchContracts,
    fetchContract,
    saveContract,
    editContract,
    removeContract,
    changePhase,
    fetchEvents,
    // Repository management
    addRemoteRepo,
    addLocalRepo,
    createManagedRepo,
    removeRepo,
    setRepoPrimary,
    // Task association
    addTask,
    removeTask,
  };
}