Files
rheinsw-mono-repo/internal_frontend/app/customers/page.tsx

192 lines
9.0 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 {useErrorHandler} from "@/components/error-boundary";
import {showError, showInfoToast} from "@/lib/ui/showToast";
import {useCallback} from "react";
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;
const handleError = useErrorHandler();
// Wrap the loadCustomers function with useCallback
const loadCustomers = useCallback(async () => {
setLoading(true);
try {
const response = await fetch('/api/customers');
if (!response.ok) {
showError("Failed to fetch customers data");
throw new Error(`Failed to fetch customers: ${response.statusText}`);
}
const data = await response.json();
setCustomers(data);
} catch (error) {
showError("Failed to fetch customers data (1)");
handleError(error);
setCustomers([]);
} finally {
setLoading(false);
}
}, [handleError]); // Include handleError as a dependency
useEffect(() => {
loadCustomers();
}, [loadCustomers]); // Add loadCustomers to the dependency array
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 onCustomerCreated={loadCustomers}/>
</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>
);
}