import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query';
import { deepCompare } from '@utils/common/object.util';
import { useEffect, useMemo } from 'react';
type EditionData = {
isEditing: boolean;
};
type UseEditionOptions = {
/**
* Crear snapshot inicial al montar
*/
snapshotOnMount?: boolean;
};
/**
* Custom hook para el manejo de la edición de la pre póliza.
*
* @param {string} key - La "key" para los datos de edición.
* @param {Partial<T>} [data] - La data inicial para la edición. Inicializado por defecto con un objeto vacío.
* @param {UseEditionOptions} [options] - Opciones para el hook. Inicializado por defecto con un objeto vacío.
*/
export function useEdition<T extends object = Record<string, unknown>>(
key: string,
data: Partial<T> = {},
options: UseEditionOptions = {},
): {
/**
* La "queryKey" para los datos de edición.
*/
baseKey: readonly ['edition', string];
/**
* La "queryKey" para los datos del snapshot.
*/
snapKey: readonly ['edition', string, 'snapshot'];
/**
* La "queryKey" para los datos que se envían a editar.
*/
sendToEditKey: readonly ['edition', string, 'send-to-edit'];
/**
* Una función para obtener los datos de edición actuales.
*/
getCurrent: () => T | undefined;
/**
* Una función para obtener los datos de edición del snapshot.
*/
getSnapshot: () => T | undefined;
/**
* Una función para obtener los datos que se envían a editar.
*/
getDataToEdit: () => T | undefined;
/**
* Una función para verificar si los datos de edición han sido modificados.
*/
isModified: () => boolean;
/**
* Una función para obtener si la edición está siendo editada.
*/
getIsEditing: () => boolean | undefined;
/**
* Una función para actualizar los datos de edición.
*
* @param {Partial<T>} editionData - Los nuevos datos de edición para establecer.
*/
setEditionData: (editionData: Partial<T>) => void;
/**
* Una función para tomar un snapshot de los datos de edición y poder compararlos con los datos actuales.
*/
takeSnapshot: () => void;
/**
* El QueryClient utilizado por el hook.
*/
queryClient: QueryClient;
} {
const queryClient = useQueryClient();
const equals = deepCompare;
const baseKey = useMemo(() => ['edition', key] as const, [key]);
const snapKey = useMemo(() => ['edition', key, 'snapshot'] as const, [key]);
const sendToEditKey = useMemo(() => ['edition', key, 'send-to-edit'] as const, [key]);
useQuery<Partial<T>>({
queryKey: baseKey,
initialData: () => queryClient.getQueryData<T>(baseKey) ?? { ...data, isEditing: false },
staleTime: Infinity,
});
useQuery<Partial<T>>({
queryKey: snapKey,
initialData: () => queryClient.getQueryData<T>(baseKey) ?? { ...data },
staleTime: Infinity,
});
// Inicializa el estado base una vez montado
useEffect(() => {
const current = queryClient.getQueryData<T>(baseKey);
if (data && Object.keys(data).length) {
queryClient.setQueryData<Partial<T>>(baseKey, {
...current,
...data,
});
}
// Snapshot opcional al montar
if (options.snapshotOnMount) {
const afterInit = queryClient.getQueryData<T>(baseKey);
if (afterInit) {
const { isEditing, ...payload } = afterInit as T & EditionData; // eslint-disable-line @typescript-eslint/no-unused-vars
queryClient.setQueryData<T>(snapKey, payload as T);
queryClient.setQueryData<T>(baseKey, {
...afterInit,
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // intencional: solo al montar
const getCurrent = () => queryClient.getQueryData<T>(baseKey) as T | undefined;
const getSnapshot = () => queryClient.getQueryData<T>(snapKey) as T | undefined;
const getIsEditing = () => queryClient.getQueryData<T & EditionData>(baseKey)?.isEditing as boolean | undefined;
const getDataToEdit = () => queryClient.getQueryData<T>(sendToEditKey) as T | undefined;
const setEditionData = (editionData: Partial<T>) => {
const current = getCurrent();
const merged = {
...current,
...editionData,
} as T;
queryClient.setQueryData<T>(baseKey, merged);
handleSendDataToEdit(editionData, getSnapshot() as T);
};
const takeSnapshot = () => {
const current = getCurrent();
if (!current) return;
const plain = { ...current };
const { isEditing, ...payload } = plain as T & EditionData; // eslint-disable-line @typescript-eslint/no-unused-vars
queryClient.setQueryData<T>(snapKey, payload as T);
cleanDataToSend();
};
const isModified = () => {
const current = getCurrent();
const snap = getSnapshot();
if (!current || !snap) return false;
const plainCurrent = { ...current };
const { isEditing, ...payload } = plainCurrent as T & EditionData; // eslint-disable-line @typescript-eslint/no-unused-vars
const { isEditing: snapIsEditing, ...restSnap } = snap as T & EditionData; // eslint-disable-line @typescript-eslint/no-unused-vars
return !equals(payload, restSnap);
};
const handleSendDataToEdit = (data: Partial<T>, dataToCompare: Partial<T>) => {
if (!queryClient.getQueryData(sendToEditKey)) {
queryClient.setQueryData<Partial<T>>(sendToEditKey, { ...data });
}
Object.entries(dataToCompare).forEach(([dataToCompareKey, dataToCompareValue]) => {
Object.entries(data).forEach(([dataKey, dataValue]) => {
if (dataKey === dataToCompareKey) {
if (equals(dataValue, dataToCompareValue)) {
delete (queryClient.getQueryData(sendToEditKey) as any)[dataKey];
} else {
queryClient.setQueryData<Partial<T>>(sendToEditKey, {
...queryClient.getQueryData(sendToEditKey),
...data,
});
}
}
});
});
};
const cleanDataToSend = () => {
queryClient.removeQueries({
queryKey: sendToEditKey,
});
};
return {
baseKey,
snapKey,
sendToEditKey,
getCurrent,
getSnapshot,
getDataToEdit,
isModified,
getIsEditing,
setEditionData,
takeSnapshot,
queryClient,
};
}