367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback } from "react";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import type { Transaction } from "@/lib/types";
|
|
import { getTransactionsQueryKey } from "@/lib/hooks";
|
|
import type { TransactionsPaginatedParams } from "@/services/banking.service";
|
|
import { invalidateAllTransactionQueries } from "@/lib/cache-utils";
|
|
|
|
interface UseTransactionMutationsProps {
|
|
transactionParams: TransactionsPaginatedParams;
|
|
transactionsData: { transactions: Transaction[]; total: number } | undefined;
|
|
}
|
|
|
|
export function useTransactionMutations({
|
|
transactionParams,
|
|
transactionsData,
|
|
}: UseTransactionMutationsProps) {
|
|
const queryClient = useQueryClient();
|
|
const [updatingTransactionIds, setUpdatingTransactionIds] = useState<
|
|
Set<string>
|
|
>(new Set());
|
|
|
|
const toggleReconciled = useCallback(
|
|
async (transactionId: string) => {
|
|
if (!transactionsData) return;
|
|
|
|
const transaction = transactionsData.transactions.find(
|
|
(t) => t.id === transactionId
|
|
);
|
|
if (!transaction) return;
|
|
|
|
const newReconciledState = !transaction.isReconciled;
|
|
const updatedTransaction = {
|
|
...transaction,
|
|
isReconciled: newReconciledState,
|
|
};
|
|
|
|
// Optimistic cache update
|
|
const queryKey = getTransactionsQueryKey(transactionParams);
|
|
const previousData =
|
|
queryClient.getQueryData<typeof transactionsData>(queryKey);
|
|
|
|
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
|
|
if (!oldData) return oldData;
|
|
return {
|
|
...oldData,
|
|
transactions: oldData.transactions.map((t) =>
|
|
t.id === transactionId
|
|
? { ...t, isReconciled: newReconciledState }
|
|
: t
|
|
),
|
|
};
|
|
});
|
|
|
|
try {
|
|
const response = await fetch("/api/banking/transactions", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(updatedTransaction),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// TOUJOURS revalider après succès pour garantir la cohérence
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} catch (error) {
|
|
console.error("Failed to update transaction:", error);
|
|
// Rollback on error
|
|
if (previousData) {
|
|
queryClient.setQueryData(queryKey, previousData);
|
|
}
|
|
invalidateAllTransactionQueries(queryClient);
|
|
}
|
|
},
|
|
[transactionsData, transactionParams, queryClient]
|
|
);
|
|
|
|
const markReconciled = useCallback(
|
|
async (transactionId: string) => {
|
|
if (!transactionsData) return;
|
|
|
|
const transaction = transactionsData.transactions.find(
|
|
(t) => t.id === transactionId
|
|
);
|
|
if (!transaction || transaction.isReconciled) return;
|
|
|
|
const updatedTransaction = {
|
|
...transaction,
|
|
isReconciled: true,
|
|
};
|
|
|
|
// Optimistic cache update
|
|
const queryKey = getTransactionsQueryKey(transactionParams);
|
|
const previousData =
|
|
queryClient.getQueryData<typeof transactionsData>(queryKey);
|
|
|
|
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
|
|
if (!oldData) return oldData;
|
|
return {
|
|
...oldData,
|
|
transactions: oldData.transactions.map((t) =>
|
|
t.id === transactionId ? { ...t, isReconciled: true } : t
|
|
),
|
|
};
|
|
});
|
|
|
|
try {
|
|
const response = await fetch("/api/banking/transactions", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(updatedTransaction),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// TOUJOURS revalider après succès pour garantir la cohérence
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} catch (error) {
|
|
console.error("Failed to update transaction:", error);
|
|
// Rollback on error
|
|
if (previousData) {
|
|
queryClient.setQueryData(queryKey, previousData);
|
|
}
|
|
invalidateAllTransactionQueries(queryClient);
|
|
}
|
|
},
|
|
[transactionsData, transactionParams, queryClient]
|
|
);
|
|
|
|
const setCategory = useCallback(
|
|
async (transactionId: string, categoryId: string | null) => {
|
|
if (!transactionsData) return;
|
|
|
|
const transaction = transactionsData.transactions.find(
|
|
(t) => t.id === transactionId
|
|
);
|
|
if (!transaction) return;
|
|
|
|
setUpdatingTransactionIds((prev) => new Set(prev).add(transactionId));
|
|
|
|
// Optimistic cache update
|
|
const queryKey = getTransactionsQueryKey(transactionParams);
|
|
const previousData =
|
|
queryClient.getQueryData<typeof transactionsData>(queryKey);
|
|
|
|
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
|
|
if (!oldData) return oldData;
|
|
|
|
return {
|
|
...oldData,
|
|
transactions: oldData.transactions.map((t) =>
|
|
t.id === transactionId ? { ...t, categoryId } : t
|
|
),
|
|
};
|
|
});
|
|
|
|
try {
|
|
const response = await fetch("/api/banking/transactions", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ ...transaction, categoryId }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// TOUJOURS revalider après succès pour garantir la cohérence
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} catch (error) {
|
|
console.error("Failed to update transaction:", error);
|
|
// Rollback on error
|
|
if (previousData) {
|
|
queryClient.setQueryData(queryKey, previousData);
|
|
}
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} finally {
|
|
setUpdatingTransactionIds((prev) => {
|
|
const next = new Set(prev);
|
|
next.delete(transactionId);
|
|
return next;
|
|
});
|
|
}
|
|
},
|
|
[transactionsData, transactionParams, queryClient]
|
|
);
|
|
|
|
const deleteTransaction = useCallback(
|
|
async (transactionId: string) => {
|
|
if (!transactionsData) return;
|
|
|
|
// Save current data for rollback
|
|
const queryKey = getTransactionsQueryKey(transactionParams);
|
|
const previousData =
|
|
queryClient.getQueryData<typeof transactionsData>(queryKey);
|
|
|
|
// Optimistic cache update
|
|
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
|
|
if (!oldData) return oldData;
|
|
return {
|
|
...oldData,
|
|
transactions: oldData.transactions.filter(
|
|
(t) => t.id !== transactionId
|
|
),
|
|
total: oldData.total - 1,
|
|
};
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`/api/banking/transactions?id=${transactionId}`,
|
|
{
|
|
method: "DELETE",
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(
|
|
errorData.error ||
|
|
`Failed to delete transaction: ${response.status}`
|
|
);
|
|
}
|
|
|
|
// TOUJOURS revalider après succès pour garantir la cohérence
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} catch (error) {
|
|
console.error("Failed to delete transaction:", error);
|
|
// Rollback on error
|
|
if (previousData) {
|
|
queryClient.setQueryData(queryKey, previousData);
|
|
}
|
|
invalidateAllTransactionQueries(queryClient);
|
|
}
|
|
},
|
|
[transactionsData, transactionParams, queryClient]
|
|
);
|
|
|
|
const bulkReconcile = useCallback(
|
|
async (reconciled: boolean, selectedTransactionIds: Set<string>) => {
|
|
if (!transactionsData) return;
|
|
|
|
const transactionsToUpdate = transactionsData.transactions.filter((t) =>
|
|
selectedTransactionIds.has(t.id)
|
|
);
|
|
|
|
const transactionIds = transactionsToUpdate.map((t) => t.id);
|
|
|
|
// Optimistic cache update
|
|
const queryKey = getTransactionsQueryKey(transactionParams);
|
|
const previousData =
|
|
queryClient.getQueryData<typeof transactionsData>(queryKey);
|
|
|
|
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
|
|
if (!oldData) return oldData;
|
|
return {
|
|
...oldData,
|
|
transactions: oldData.transactions.map((t) =>
|
|
transactionIds.includes(t.id)
|
|
? { ...t, isReconciled: reconciled }
|
|
: t
|
|
),
|
|
};
|
|
});
|
|
|
|
try {
|
|
await Promise.all(
|
|
transactionsToUpdate.map((t) =>
|
|
fetch("/api/banking/transactions", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ ...t, isReconciled: reconciled }),
|
|
})
|
|
)
|
|
);
|
|
|
|
// TOUJOURS revalider après succès pour garantir la cohérence
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} catch (error) {
|
|
console.error("Failed to update transactions:", error);
|
|
// Rollback on error
|
|
if (previousData) {
|
|
queryClient.setQueryData(queryKey, previousData);
|
|
}
|
|
invalidateAllTransactionQueries(queryClient);
|
|
}
|
|
},
|
|
[transactionsData, transactionParams, queryClient]
|
|
);
|
|
|
|
const bulkSetCategory = useCallback(
|
|
async (categoryId: string | null, selectedTransactionIds: Set<string>) => {
|
|
if (!transactionsData) return;
|
|
|
|
const transactionsToUpdate = transactionsData.transactions.filter((t) =>
|
|
selectedTransactionIds.has(t.id)
|
|
);
|
|
|
|
const transactionIds = transactionsToUpdate.map((t) => t.id);
|
|
setUpdatingTransactionIds((prev) => {
|
|
const next = new Set(prev);
|
|
transactionIds.forEach((id) => next.add(id));
|
|
return next;
|
|
});
|
|
|
|
// Optimistic cache update
|
|
const queryKey = getTransactionsQueryKey(transactionParams);
|
|
const previousData =
|
|
queryClient.getQueryData<typeof transactionsData>(queryKey);
|
|
|
|
queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
|
|
if (!oldData) return oldData;
|
|
return {
|
|
...oldData,
|
|
transactions: oldData.transactions.map((t) =>
|
|
transactionIds.includes(t.id) ? { ...t, categoryId } : t
|
|
),
|
|
};
|
|
});
|
|
|
|
try {
|
|
await Promise.all(
|
|
transactionsToUpdate.map((t) =>
|
|
fetch("/api/banking/transactions", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ ...t, categoryId }),
|
|
})
|
|
)
|
|
);
|
|
|
|
// TOUJOURS revalider après succès pour garantir la cohérence
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} catch (error) {
|
|
console.error("Failed to update transactions:", error);
|
|
// Rollback on error
|
|
if (previousData) {
|
|
queryClient.setQueryData(queryKey, previousData);
|
|
}
|
|
invalidateAllTransactionQueries(queryClient);
|
|
} finally {
|
|
setUpdatingTransactionIds((prev) => {
|
|
const next = new Set(prev);
|
|
transactionIds.forEach((id) => next.delete(id));
|
|
return next;
|
|
});
|
|
}
|
|
},
|
|
[transactionsData, transactionParams, queryClient]
|
|
);
|
|
|
|
return {
|
|
toggleReconciled,
|
|
markReconciled,
|
|
setCategory,
|
|
deleteTransaction,
|
|
bulkReconcile,
|
|
bulkSetCategory,
|
|
updatingTransactionIds,
|
|
};
|
|
}
|