Files
rheinsw-mono-repo/internal_frontend/app/customers/page.tsx
Thatsaphorn Atchariyaphap 328c0537ba 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.
2025-07-07 22:02:55 +02:00

179 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import {useState, useEffect, useMemo} from "react";
import {Button} from "@/components/ui/button";
import {Input} from "@/components/ui/input";
import {Card, CardContent} from "@/components/ui/card";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationNext,
PaginationPrevious
} from "@/components/ui/pagination";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from "@/components/ui/table";
import {motion} from "framer-motion";
import {ArrowRight} from "lucide-react";
import {NewCustomerModal} from "@/components/customers/modal/NewCustomerModal";
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<Customer[]>([]);
const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const pageSize = 15;
useEffect(() => {
setLoading(true);
CustomerRepository.getAll()
.then((data) => {
setCustomers(data);
})
.catch((error) => {
console.error('Error fetching customers:', error);
setCustomers([]);
})
.finally(() => {
setLoading(false);
});
}, []);
const filtered = useMemo(() => {
if (customers.length === 0) return [];
return customers.filter(
(c) =>
c.name.toLowerCase().includes(search.toLowerCase()) ||
c.email.toLowerCase().includes(search.toLowerCase()) ||
c.companyName.toLowerCase().includes(search.toLowerCase()) ||
c.street.toLowerCase().includes(search.toLowerCase()) ||
c.zip.toLowerCase().includes(search.toLowerCase()) ||
c.city.toLowerCase().includes(search.toLowerCase()) ||
c.phoneNumbers?.[0]?.number?.toLowerCase().includes(search.toLowerCase())
);
}, [customers, search]);
const paginated = useMemo(() => {
const start = (page - 1) * pageSize;
return filtered.slice(start, start + pageSize);
}, [filtered, page]);
const totalPages = Math.ceil(filtered.length / pageSize);
return (
<div className="p-6 space-y-6 text-sm overflow-x-auto">
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{duration: 0.3}}
className="grid grid-cols-1 md:grid-cols-3 gap-4"
>
<Card>
<CardContent>
<div className="text-sm text-muted-foreground">Kunden</div>
<div className="text-3xl font-bold">{customers.length}</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="text-sm text-muted-foreground">Demo-Statistik</div>
<div className="text-3xl font-bold"></div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="text-sm text-muted-foreground">Letzte Aktivität</div>
<div className="text-3xl font-bold"></div>
</CardContent>
</Card>
</motion.div>
<motion.div initial={{opacity: 0}} animate={{opacity: 1}} transition={{duration: 0.3}}>
<Card>
<CardContent className="space-y-6">
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
<Input placeholder="Suche" value={search} onChange={(e) => setSearch(e.target.value)}/>
<NewCustomerModal/>
</div>
{customers.length === 0 && loading ? (
<div className="text-center text-muted-foreground">Lade Kunden...</div>
) : (
<div className="overflow-x-auto">
<Table className="text-xs min-w-[700px]">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>E-Mail</TableHead>
<TableHead>Firma</TableHead>
<TableHead>Telefon</TableHead>
<TableHead>Straße</TableHead>
<TableHead>PLZ</TableHead>
<TableHead>Ort</TableHead>
<TableHead>Erstellt am</TableHead>
<TableHead>Aktionen</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{paginated.map((customer) => (
<TableRow key={customer.id}>
<TableCell
className="max-w-[180px] truncate">{customer.name}</TableCell>
<TableCell
className="max-w-[180px] truncate">{customer.email}</TableCell>
<TableCell
className="max-w-[180px] truncate">{customer.companyName}</TableCell>
<TableCell className="max-w-[140px] truncate">
{customer.phoneNumbers?.[0]?.number}
</TableCell>
<TableCell
className="max-w-[180px] truncate">{customer.street}</TableCell>
<TableCell>{customer.zip}</TableCell>
<TableCell>{customer.city}</TableCell>
<TableCell>{new Date(customer.createdAt).toLocaleString()}</TableCell>
<TableCell>
<Link href={`/customers/${customer.id}`}>
<Button variant="ghost" size="icon">
<ArrowRight className="w-4 h-4"/>
</Button>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
<Pagination className="justify-center pt-4">
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => setPage((p) => Math.max(1, p - 1))}
className={page === 1 ? "pointer-events-none opacity-50" : ""}
/>
</PaginationItem>
<PaginationItem>
<PaginationNext
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
className={page === totalPages ? "pointer-events-none opacity-50" : ""}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</CardContent>
</Card>
</motion.div>
</div>
);
}