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>
452 lines
16 KiB
TypeScript
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,
|
|
});
|
|
}
|