From 328c0537ba99142b8dbb822d97be85e60e2ec56a Mon Sep 17 00:00:00 2001 From: Thatsaphorn Atchariyaphap Date: Mon, 7 Jul 2025 22:02:55 +0200 Subject: [PATCH] Introduce caching in `CustomerRepository` and refactor API integration - Add in-memory caching for customer data in `CustomerRepository` to reduce API calls. - Replace direct API calls with methods from `CustomerRepository`. - Update customer-related pages (`[id]/page.tsx`, `customers/page.tsx`) to use `CustomerRepository` for data fetching. - Adjust breadcrumb resolver to leverage `CustomerRepository`. - Remove `axios` dependency from customer-related components. --- internal_frontend/app/customers/[id]/page.tsx | 41 +++++----- internal_frontend/app/customers/page.tsx | 10 +-- .../lib/navigation/breadcrumb-map.ts | 9 ++- .../repositories/customerRepository.ts | 81 ++++++++++++++++++- 4 files changed, 106 insertions(+), 35 deletions(-) diff --git a/internal_frontend/app/customers/[id]/page.tsx b/internal_frontend/app/customers/[id]/page.tsx index c10068c..51bec34 100644 --- a/internal_frontend/app/customers/[id]/page.tsx +++ b/internal_frontend/app/customers/[id]/page.tsx @@ -1,19 +1,17 @@ "use client"; -import React, {useEffect, useState, useMemo} from "react"; +import React, {useEffect, useState} from "react"; import {useParams, useRouter} from "next/navigation"; import {ChevronLeft} from "lucide-react"; -import axios, {AxiosError} from "axios"; import {Button} from "@/components/ui/button"; import {Dialog} from "@/components/ui/dialog"; import {Skeleton} from "@/components/ui/skeleton"; import CustomerDetailContent from "@/components/customers/details/CustomerDetailContent"; -import {Customer} from "@/services/customers/entities/customer"; import CustomerInformationContent from "@/components/customers/details/sub/ContactInformationContent"; import CustomerPhoneNumberContent from "@/components/customers/details/sub/CustomerPhoneNumberContent"; import CustomerNotesContent from "@/components/customers/details/sub/CustomerNotesContent"; - -const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api'; +import {Customer} from "@/services/customers/entities/customer"; +import {CustomerRepository} from "@/services/customers/repositories/customerRepository"; export default function CustomerDetailPage() { const router = useRouter(); @@ -35,23 +33,22 @@ export default function CustomerDetailPage() { setLoading(true); setError(null); - axios - .get(`${API_BASE_URL}/customers/${id}`) - .then((res) => { - if (isMounted) { - setCustomer(res.data); + CustomerRepository.getById(id) + .then((result) => { + if (!isMounted) return; + + if (!result) { + setError("Kunde nicht gefunden"); + setCustomer(null); + } else { + setCustomer(result); } }) - .catch((error: AxiosError) => { - if (isMounted) { - console.error('Error fetching customer:', error); - setCustomer(null); - setError( - error.response?.status === 404 - ? "Kunde nicht gefunden" - : "Fehler beim Laden der Kundendaten" - ); - } + .catch((error) => { + if (!isMounted) return; + console.error('Error fetching customer:', error); + setCustomer(null); + setError("Fehler beim Laden der Kundendaten"); }) .finally(() => { if (isMounted) { @@ -85,14 +82,14 @@ export default function CustomerDetailPage() { } }; - const customerMetadata = useMemo(() => ({ + const customerMetadata = { createdInfo: customer ? `Erstellt von ${customer.createdBy || "-"} am ${formatDate(customer.createdAt)}` : "Erstellt von - am -", lastActivityInfo: customer ? `Letzte Aktivität: ${customer.updatedBy || "-"} am ${formatDate(customer.updatedAt)}` : "Letzte Aktivität: - am -" - }), [customer]); + }; const renderMetadata = () => { if (loading) { diff --git a/internal_frontend/app/customers/page.tsx b/internal_frontend/app/customers/page.tsx index 41bdbfc..5dfdf96 100644 --- a/internal_frontend/app/customers/page.tsx +++ b/internal_frontend/app/customers/page.tsx @@ -22,9 +22,9 @@ import { import {motion} from "framer-motion"; import {ArrowRight} from "lucide-react"; import {NewCustomerModal} from "@/components/customers/modal/NewCustomerModal"; -import axios from "axios"; import {Customer} from "@/services/customers/entities/customer"; import Link from "next/link"; +import {CustomerRepository} from "@/services/customers/repositories/customerRepository"; export default function CustomersPage() { const [customers, setCustomers] = useState([]); @@ -35,9 +35,9 @@ export default function CustomersPage() { useEffect(() => { setLoading(true); - axios.get("/api/customers") - .then((res) => { - setCustomers(res.data); + CustomerRepository.getAll() + .then((data) => { + setCustomers(data); }) .catch((error) => { console.error('Error fetching customers:', error); @@ -176,4 +176,4 @@ export default function CustomersPage() { ); -} +} \ No newline at end of file diff --git a/internal_frontend/lib/navigation/breadcrumb-map.ts b/internal_frontend/lib/navigation/breadcrumb-map.ts index 1c6a3d0..7ccdeab 100644 --- a/internal_frontend/lib/navigation/breadcrumb-map.ts +++ b/internal_frontend/lib/navigation/breadcrumb-map.ts @@ -1,4 +1,4 @@ -import {customerRoutes} from "@/app/api/customers/customerRoutes"; +import {CustomerRepository} from "@/services/customers/repositories/customerRepository"; export const breadcrumbMap: Record = { 'dashboard': 'Dashboard', @@ -10,10 +10,11 @@ export const breadcrumbMap: Record = { export const breadcrumbResolvers: Record Promise> = { "customers": async (id: string) => { - const res = await fetch(`/api${customerRoutes.getById(id)}`, {cache: "no-store"}); - const customer = await res.json(); + const customer = await CustomerRepository.getById(id); + if (!customer) return `ID: ${id}`; + if (customer.companyName) return `Firma: ${customer.companyName}`; if (customer.name) return `Name: ${customer.name}`; return `ID: ${id}`; }, -}; +}; \ No newline at end of file diff --git a/internal_frontend/services/customers/repositories/customerRepository.ts b/internal_frontend/services/customers/repositories/customerRepository.ts index 843b40e..90f5010 100644 --- a/internal_frontend/services/customers/repositories/customerRepository.ts +++ b/internal_frontend/services/customers/repositories/customerRepository.ts @@ -1,13 +1,86 @@ import {Customer} from "@/services/customers/entities/customer"; import {CreateCustomerDto} from "@/services/customers/dtos/createCustomer.dto"; -import {callApi} from "@/lib/api/callApi"; export class CustomerRepository { + private static readonly customerCache: Map = new Map(); + private static lastFetchAll: number | null = null; + private static readonly CACHE_DURATION = 10 * 1000; // seconds in milliseconds + static async getAll(): Promise { - return await callApi("/customers", "GET"); + const now = Date.now(); + if (this.lastFetchAll && (now - this.lastFetchAll < this.CACHE_DURATION)) { + return Array.from(this.customerCache.values()); + } + + console.log('Cache expired or not initialized, fetching fresh data from API'); + + const response = await fetch('/api/customers'); + if (!response.ok) { + console.error('Failed to fetch customers:', response.status, response.statusText); + return Promise.reject(new Error(`Failed to fetch customers: ${response.statusText}`)); + } + + const customers: Customer[] = await response.json(); + this.customerCache.clear(); + customers.forEach((customer: Customer) => this.customerCache.set(customer.id, customer)); + this.lastFetchAll = now; + return customers; + } + + static async getById(id: string): Promise { + const cachedCustomer = this.customerCache.get(id); + if (cachedCustomer) { + return cachedCustomer; + } + + console.log(`Cache miss for customer ${id}, fetching from API`); + + try { + const response = await fetch(`/api/customers/${id}`); + if (!response.ok) { + if (response.status === 404) { + console.log(`Customer ${id} not found`); + return null; + } + console.error('Failed to fetch customer:', response.status, response.statusText); + return Promise.reject(new Error(`Failed to fetch customer: ${response.statusText}`)); + } + + const customer: Customer = await response.json(); + this.customerCache.set(id, customer); + return customer; + } catch (error) { + console.error('Error fetching customer:', error); + return Promise.reject(error); + } } static async create(payload: CreateCustomerDto): Promise { - await callApi("/customers", "POST", payload); + const response = await fetch('/api/customers', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + console.error('Failed to create customer:', response.status, response.statusText); + return Promise.reject(new Error(`Failed to create customer: ${response.statusText}`)); + } + + console.log('Cache invalidated after creating new customer'); + this.lastFetchAll = null; // Invalidate the cache after creating new customer } -} + + static clearCache(): void { + console.log('Cache manually cleared'); + this.customerCache.clear(); + this.lastFetchAll = null; + } + + static updateCache(customer: Customer): void { + console.log(`Cache updated for customer ${customer.id}`); + this.customerCache.set(customer.id, customer); + } +} \ No newline at end of file