erp-construccion-frontend-v2/web/src/hooks/useContracts.ts
Adrian Flores Cortes 55261598a2 [FIX] fix: Resolve TypeScript errors for successful build
Fixes:
- Add teal, cyan, slate colors to StatusColor type and StatusBadge
- Create StatsCard component with color prop for backward compatibility
- Add label/required props to FormGroup component
- Fix Pagination to accept both currentPage and page props
- Fix unused imports in quality and contracts pages
- Add missing Plus, Trash2, User icon imports in contracts pages
- Remove duplicate formatDate function in ContratoDetailPage

New components:
- StatsCard, StatsCardGrid for statistics display

Build: Success (npm run build passes)
Dev: Success (npm run dev starts on port 3020)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:36:21 -06:00

452 lines
16 KiB
TypeScript

/**
* useContracts Hook - Contratos, Subcontratistas, Partidas, Addendas
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import toast from 'react-hot-toast';
import type { ApiError } from '../services/api';
import {
contractsApi,
subcontractorsApi,
contractPartidasApi,
contractAddendumsApi,
} from '../services/contracts';
import type {
ContractFilters,
CreateContractDto,
UpdateContractDto,
SubcontractorFilters,
CreateSubcontractorDto,
UpdateSubcontractorDto,
CreateContractPartidaDto,
UpdateContractPartidaDto,
CreateAddendumDto,
UpdateAddendumDto,
} from '../types/contracts.types';
// ============================================================================
// QUERY KEYS
// ============================================================================
export const contractsKeys = {
// Contracts
contracts: {
all: ['contracts'] as const,
list: (filters?: ContractFilters) => [...contractsKeys.contracts.all, 'list', filters] as const,
detail: (id: string) => [...contractsKeys.contracts.all, 'detail', id] as const,
stats: () => [...contractsKeys.contracts.all, 'stats'] as const,
},
// Subcontractors
subcontractors: {
all: ['subcontractors'] as const,
list: (filters?: SubcontractorFilters) => [...contractsKeys.subcontractors.all, 'list', filters] as const,
detail: (id: string) => [...contractsKeys.subcontractors.all, 'detail', id] as const,
},
// Contract Partidas
partidas: {
all: ['contract-partidas'] as const,
list: (contractId: string) => [...contractsKeys.partidas.all, 'list', contractId] as const,
},
// Contract Addendums
addendums: {
all: ['contract-addendums'] as const,
list: (contractId: string) => [...contractsKeys.addendums.all, 'list', contractId] as const,
detail: (contractId: string, addendumId: string) => [...contractsKeys.addendums.all, 'detail', contractId, addendumId] as const,
},
};
// ============================================================================
// ERROR HANDLER
// ============================================================================
const handleError = (error: AxiosError<ApiError>) => {
const message = error.response?.data?.message || 'Ha ocurrido un error';
toast.error(message);
};
// ============================================================================
// CONTRACTS HOOKS
// ============================================================================
export function useContracts(filters?: ContractFilters) {
return useQuery({
queryKey: contractsKeys.contracts.list(filters),
queryFn: () => contractsApi.list(filters),
});
}
export function useContract(id: string) {
return useQuery({
queryKey: contractsKeys.contracts.detail(id),
queryFn: () => contractsApi.get(id),
enabled: !!id,
});
}
export function useContractStats() {
return useQuery({
queryKey: contractsKeys.contracts.stats(),
queryFn: () => contractsApi.stats(),
});
}
export function useCreateContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateContractDto) => contractsApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
toast.success('Contrato creado exitosamente');
},
onError: handleError,
});
}
export function useUpdateContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateContractDto }) =>
contractsApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(id) });
toast.success('Contrato actualizado');
},
onError: handleError,
});
}
export function useDeleteContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => contractsApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
toast.success('Contrato eliminado');
},
onError: handleError,
});
}
export function useSubmitContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => contractsApi.submit(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(id) });
toast.success('Contrato enviado a revision');
},
onError: handleError,
});
}
export function useApproveContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => contractsApi.approve(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(id) });
toast.success('Contrato aprobado');
},
onError: handleError,
});
}
export function useActivateContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => contractsApi.activate(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(id) });
toast.success('Contrato activado');
},
onError: handleError,
});
}
export function useCompleteContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => contractsApi.complete(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(id) });
toast.success('Contrato completado');
},
onError: handleError,
});
}
export function useTerminateContract() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, reason }: { id: string; reason: string }) =>
contractsApi.terminate(id, reason),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(id) });
toast.success('Contrato terminado');
},
onError: handleError,
});
}
// ============================================================================
// SUBCONTRACTORS HOOKS
// ============================================================================
export function useSubcontractors(filters?: SubcontractorFilters) {
return useQuery({
queryKey: contractsKeys.subcontractors.list(filters),
queryFn: () => subcontractorsApi.list(filters),
});
}
export function useSubcontractor(id: string) {
return useQuery({
queryKey: contractsKeys.subcontractors.detail(id),
queryFn: () => subcontractorsApi.get(id),
enabled: !!id,
});
}
export function useCreateSubcontractor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateSubcontractorDto) => subcontractorsApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.all });
toast.success('Subcontratista creado exitosamente');
},
onError: handleError,
});
}
export function useUpdateSubcontractor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateSubcontractorDto }) =>
subcontractorsApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.detail(id) });
toast.success('Subcontratista actualizado');
},
onError: handleError,
});
}
export function useDeleteSubcontractor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => subcontractorsApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.all });
toast.success('Subcontratista eliminado');
},
onError: handleError,
});
}
export function useActivateSubcontractor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => subcontractorsApi.activate(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.detail(id) });
toast.success('Subcontratista activado');
},
onError: handleError,
});
}
export function useDeactivateSubcontractor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => subcontractorsApi.deactivate(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.detail(id) });
toast.success('Subcontratista desactivado');
},
onError: handleError,
});
}
export function useBlacklistSubcontractor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, reason }: { id: string; reason: string }) =>
subcontractorsApi.blacklist(id, reason),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.all });
queryClient.invalidateQueries({ queryKey: contractsKeys.subcontractors.detail(id) });
toast.success('Subcontratista agregado a lista negra');
},
onError: handleError,
});
}
// ============================================================================
// CONTRACT PARTIDAS HOOKS
// ============================================================================
export function useContractPartidas(contractId: string) {
return useQuery({
queryKey: contractsKeys.partidas.list(contractId),
queryFn: () => contractPartidasApi.list(contractId),
enabled: !!contractId,
});
}
export function useCreateContractPartida() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, data }: { contractId: string; data: CreateContractPartidaDto }) =>
contractPartidasApi.create(contractId, data),
onSuccess: (_, { contractId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.partidas.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(contractId) });
toast.success('Partida agregada');
},
onError: handleError,
});
}
export function useUpdateContractPartida() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, partidaId, data }: { contractId: string; partidaId: string; data: UpdateContractPartidaDto }) =>
contractPartidasApi.update(contractId, partidaId, data),
onSuccess: (_, { contractId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.partidas.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(contractId) });
toast.success('Partida actualizada');
},
onError: handleError,
});
}
export function useDeleteContractPartida() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, partidaId }: { contractId: string; partidaId: string }) =>
contractPartidasApi.delete(contractId, partidaId),
onSuccess: (_, { contractId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.partidas.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(contractId) });
toast.success('Partida eliminada');
},
onError: handleError,
});
}
// ============================================================================
// CONTRACT ADDENDUMS HOOKS
// ============================================================================
export function useContractAddendums(contractId: string) {
return useQuery({
queryKey: contractsKeys.addendums.list(contractId),
queryFn: () => contractAddendumsApi.list(contractId),
enabled: !!contractId,
});
}
export function useContractAddendum(contractId: string, addendumId: string) {
return useQuery({
queryKey: contractsKeys.addendums.detail(contractId, addendumId),
queryFn: () => contractAddendumsApi.get(contractId, addendumId),
enabled: !!contractId && !!addendumId,
});
}
export function useCreateContractAddendum() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, data }: { contractId: string; data: CreateAddendumDto }) =>
contractAddendumsApi.create(contractId, data),
onSuccess: (_, { contractId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(contractId) });
toast.success('Addenda creada');
},
onError: handleError,
});
}
export function useUpdateContractAddendum() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, addendumId, data }: { contractId: string; addendumId: string; data: UpdateAddendumDto }) =>
contractAddendumsApi.update(contractId, addendumId, data),
onSuccess: (_, { contractId, addendumId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.detail(contractId, addendumId) });
toast.success('Addenda actualizada');
},
onError: handleError,
});
}
export function useDeleteContractAddendum() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, addendumId }: { contractId: string; addendumId: string }) =>
contractAddendumsApi.delete(contractId, addendumId),
onSuccess: (_, { contractId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.list(contractId) });
toast.success('Addenda eliminada');
},
onError: handleError,
});
}
export function useSubmitAddendum() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, addendumId }: { contractId: string; addendumId: string }) =>
contractAddendumsApi.submit(contractId, addendumId),
onSuccess: (_, { contractId, addendumId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.detail(contractId, addendumId) });
toast.success('Addenda enviada a revision');
},
onError: handleError,
});
}
export function useApproveAddendum() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, addendumId }: { contractId: string; addendumId: string }) =>
contractAddendumsApi.approve(contractId, addendumId),
onSuccess: (_, { contractId, addendumId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.detail(contractId, addendumId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.contracts.detail(contractId) });
toast.success('Addenda aprobada');
},
onError: handleError,
});
}
export function useRejectAddendum() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ contractId, addendumId, reason }: { contractId: string; addendumId: string; reason: string }) =>
contractAddendumsApi.reject(contractId, addendumId, reason),
onSuccess: (_, { contractId, addendumId }) => {
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.list(contractId) });
queryClient.invalidateQueries({ queryKey: contractsKeys.addendums.detail(contractId, addendumId) });
toast.success('Addenda rechazada');
},
onError: handleError,
});
}