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,
};
}