2026-07-23 08:42:26 +02:00
|
|
|
/**
|
2026-08-13 23:08:29 +02:00
|
|
|
* Entity History hooks — undo/restore/trash functionality.
|
2026-07-23 08:42:26 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
import { apiGet, apiPost, apiClient } from './client';
|
|
|
|
|
|
|
|
|
|
export interface EntityHistoryEntry {
|
|
|
|
|
id: string;
|
|
|
|
|
entity_type: string;
|
|
|
|
|
entity_id: string;
|
|
|
|
|
action: 'create' | 'update' | 'delete';
|
2026-08-13 23:08:29 +02:00
|
|
|
snapshot_before: Record<string, unknown> | null;
|
|
|
|
|
snapshot_after: Record<string, unknown> | null;
|
|
|
|
|
changes: Record<string, { old: unknown; new: unknown }> | null;
|
2026-07-23 08:42:26 +02:00
|
|
|
user_id: string | null;
|
|
|
|
|
created_at: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface EntityHistoryList {
|
|
|
|
|
items: EntityHistoryEntry[];
|
|
|
|
|
total: number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-13 23:08:29 +02:00
|
|
|
export interface TrashItem {
|
|
|
|
|
history_id: string;
|
|
|
|
|
entity_type: string;
|
|
|
|
|
entity_id: string;
|
|
|
|
|
snapshot_before: Record<string, unknown> | null;
|
|
|
|
|
deleted_at: string | null;
|
|
|
|
|
user_id: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TrashListResponse {
|
|
|
|
|
items: TrashItem[];
|
|
|
|
|
total: number;
|
|
|
|
|
limit: number;
|
|
|
|
|
offset: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BulkRestoreResultItem {
|
|
|
|
|
history_id: string;
|
|
|
|
|
success: boolean;
|
|
|
|
|
entity_type: string | null;
|
|
|
|
|
entity_id: string | null;
|
|
|
|
|
error: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BulkRestoreResponse {
|
|
|
|
|
total: number;
|
|
|
|
|
succeeded: number;
|
|
|
|
|
failed: number;
|
|
|
|
|
results: BulkRestoreResultItem[];
|
|
|
|
|
partial_success: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useEntityHistory(entityType?: string, entityId?: string, limit = 50) {
|
2026-07-23 08:42:26 +02:00
|
|
|
return useQuery({
|
2026-08-13 23:08:29 +02:00
|
|
|
queryKey: ['entityHistory', entityType, entityId, limit],
|
2026-07-23 08:42:26 +02:00
|
|
|
queryFn: () =>
|
2026-08-13 23:08:29 +02:00
|
|
|
apiGet<EntityHistoryList>(`/entity-history/${entityType}/${entityId}?limit=${limit}`),
|
2026-07-23 08:42:26 +02:00
|
|
|
enabled: !!entityType && !!entityId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-13 23:08:29 +02:00
|
|
|
export function useTrashList(entityType?: string, limit = 50, offset = 0) {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
params.set('limit', String(limit));
|
|
|
|
|
params.set('offset', String(offset));
|
|
|
|
|
if (entityType) {
|
|
|
|
|
params.set('entity_type', entityType);
|
|
|
|
|
}
|
|
|
|
|
return useQuery({
|
|
|
|
|
queryKey: ['trash', entityType ?? 'all', limit, offset],
|
|
|
|
|
queryFn: () => apiGet<TrashListResponse>(`/entity-history/trash?${params.toString()}`),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 08:42:26 +02:00
|
|
|
export function useRestoreFromHistory() {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
return useMutation({
|
|
|
|
|
mutationFn: (historyId: string) =>
|
|
|
|
|
apiClient.post('/entity-history/restore', { history_id: historyId }).then(r => r.data),
|
2026-08-13 23:08:29 +02:00
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['entityHistory'] });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['trash'] });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['contacts'] });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['contact'] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useBulkRestore() {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
return useMutation({
|
|
|
|
|
mutationFn: (historyIds: string[]) =>
|
|
|
|
|
apiPost<BulkRestoreResponse>('/entity-history/bulk-restore', { history_ids: historyIds }),
|
|
|
|
|
onSuccess: () => {
|
2026-07-23 08:42:26 +02:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['entityHistory'] });
|
2026-08-13 23:08:29 +02:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['trash'] });
|
2026-07-23 08:42:26 +02:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['contacts'] });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['contact'] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useUndoLastAction() {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
return useMutation({
|
|
|
|
|
mutationFn: ({ entityType, entityId }: { entityType: string; entityId: string }) =>
|
|
|
|
|
apiPost(`/entity-history/undo/${entityType}/${entityId}`, {}),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['entityHistory'] });
|
2026-08-13 23:08:29 +02:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['trash'] });
|
2026-07-23 08:42:26 +02:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['contacts'] });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['contact'] });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|