refactor: enhance cache invalidation logic across banking API routes and components for improved data consistency and performance

This commit is contained in:
Julien Froidefond
2025-12-08 14:04:12 +01:00
parent 53bae084c4
commit 8d947ad70f
14 changed files with 412 additions and 200 deletions

View File

@@ -28,6 +28,7 @@ import {
deleteCategory,
} from "@/lib/store-db";
import type { Category, Transaction } from "@/lib/types";
import { invalidateAllCategoryQueries } from "@/lib/cache-utils";
interface RecategorizationResult {
transaction: Transaction;
@@ -41,7 +42,7 @@ export default function CategoriesPage() {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingCategory, setEditingCategory] = useState<Category | null>(null);
const [expandedParents, setExpandedParents] = useState<Set<string>>(
new Set(),
new Set()
);
const [formData, setFormData] = useState({
name: "",
@@ -52,7 +53,7 @@ export default function CategoriesPage() {
});
const [searchQuery, setSearchQuery] = useState("");
const [recatResults, setRecatResults] = useState<RecategorizationResult[]>(
[],
[]
);
const [isRecatDialogOpen, setIsRecatDialogOpen] = useState(false);
const [isRecategorizing, setIsRecategorizing] = useState(false);
@@ -68,7 +69,7 @@ export default function CategoriesPage() {
};
const parents = metadata.categories.filter(
(c: Category) => c.parentId === null,
(c: Category) => c.parentId === null
);
const children: Record<string, Category[]> = {};
const orphans: Category[] = [];
@@ -77,7 +78,7 @@ export default function CategoriesPage() {
.filter((c: Category) => c.parentId !== null)
.forEach((child: Category) => {
const parentExists = parents.some(
(p: Category) => p.id === child.parentId,
(p: Category) => p.id === child.parentId
);
if (parentExists) {
if (!children[child.parentId!]) {
@@ -105,8 +106,7 @@ export default function CategoriesPage() {
}, [parentCategories.length]);
const refresh = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
queryClient.invalidateQueries({ queryKey: ["category-stats"] });
invalidateAllCategoryQueries(queryClient);
}, [queryClient]);
const getCategoryStats = useCallback(
@@ -136,7 +136,7 @@ export default function CategoriesPage() {
return { total, count };
},
[categoryStats, childrenByParent],
[categoryStats, childrenByParent]
);
if (isLoadingMetadata || !metadata || isLoadingStats || !categoryStats) {
@@ -248,7 +248,7 @@ export default function CategoriesPage() {
try {
// Fetch uncategorized transactions
const uncategorizedResponse = await fetch(
"/api/banking/transactions?limit=1000&offset=0&includeUncategorized=true",
"/api/banking/transactions?limit=1000&offset=0&includeUncategorized=true"
);
if (!uncategorizedResponse.ok) {
throw new Error("Failed to fetch uncategorized transactions");
@@ -261,11 +261,11 @@ export default function CategoriesPage() {
for (const transaction of uncategorized) {
const categoryId = autoCategorize(
transaction.description + " " + (transaction.memo || ""),
metadata.categories,
metadata.categories
);
if (categoryId) {
const category = metadata.categories.find(
(c: Category) => c.id === categoryId,
(c: Category) => c.id === categoryId
);
if (category) {
results.push({ transaction, category });
@@ -299,9 +299,9 @@ export default function CategoriesPage() {
return children.some(
(c) =>
c.name.toLowerCase().includes(query) ||
c.keywords.some((k) => k.toLowerCase().includes(query)),
c.keywords.some((k) => k.toLowerCase().includes(query))
);
},
}
);
return (
@@ -346,9 +346,9 @@ export default function CategoriesPage() {
(c) =>
c.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
c.keywords.some((k) =>
k.toLowerCase().includes(searchQuery.toLowerCase()),
k.toLowerCase().includes(searchQuery.toLowerCase())
) ||
parent.name.toLowerCase().includes(searchQuery.toLowerCase()),
parent.name.toLowerCase().includes(searchQuery.toLowerCase())
)
: allChildren;
const stats = getCategoryStats(parent.id, true);
@@ -435,7 +435,7 @@ export default function CategoriesPage() {
</p>
<p className="text-xs text-muted-foreground truncate">
{new Date(result.transaction.date).toLocaleDateString(
"fr-FR",
"fr-FR"
)}
{" • "}
{new Intl.NumberFormat("fr-FR", {