From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 11 Jan 2026 05:52:14 +0000 Subject: Contract system --- makima/frontend/src/hooks/useContracts.ts | 308 ++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 makima/frontend/src/hooks/useContracts.ts (limited to 'makima/frontend/src/hooks/useContracts.ts') diff --git a/makima/frontend/src/hooks/useContracts.ts b/makima/frontend/src/hooks/useContracts.ts new file mode 100644 index 0000000..f803527 --- /dev/null +++ b/makima/frontend/src/hooks/useContracts.ts @@ -0,0 +1,308 @@ +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([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [conflict, setConflict] = useState(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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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, + }; +} -- cgit v1.2.3