Initial commit - ERP System
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
// /opt/erp-system/app/customers/page.tsx
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Users, Plus, Mail, Building2, Phone, X, Trash2 } from 'lucide-react';
|
||||
import { useToast } from '../components/ToastProvider';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
type Customer = {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
companyName: string | null;
|
||||
phone: string | null;
|
||||
};
|
||||
|
||||
export default function CustomersPage() {
|
||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', companyName: '', phone: '' });
|
||||
const { toast, confirm } = useToast();
|
||||
const { data: session } = useSession();
|
||||
const permissions = (session?.user as any)?.permissions || [];
|
||||
const canDelete = permissions.includes('DATA_DELETE');
|
||||
|
||||
useEffect(() => {
|
||||
fetchCustomers();
|
||||
}, []);
|
||||
|
||||
const fetchCustomers = async () => {
|
||||
const res = await fetch('/api/customers');
|
||||
if (res.ok) setCustomers(await res.json());
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch('/api/customers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setFormData({ firstName: '', lastName: '', email: '', companyName: '', phone: '' });
|
||||
setShowForm(false);
|
||||
fetchCustomers();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (customer: Customer) => {
|
||||
const isConfirmed = await confirm({
|
||||
title: 'Kunde löschen',
|
||||
message: `"${customer.companyName || customer.firstName + ' ' + customer.lastName}" und alle verknüpften Daten (Tickets, Verträge, Dokumente) unwiderruflich löschen?`,
|
||||
danger: true
|
||||
});
|
||||
if (!isConfirmed) return;
|
||||
|
||||
const res = await fetch(`/api/customers?id=${customer.id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
toast('Kunde gelöscht', 'success');
|
||||
fetchCustomers();
|
||||
} else {
|
||||
const data = await res.json();
|
||||
toast(data.error || 'Fehler beim Löschen', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6 animate-fade-in-up">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 flex items-center gap-2">
|
||||
<Users className="w-6 h-6 text-indigo-600" /> Kundenverwaltung
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1">Verwalte deinen Kundenstamm und Ansprechpartner.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowForm(!showForm)}
|
||||
className="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition-colors flex items-center gap-2 font-medium shadow-sm"
|
||||
>
|
||||
{showForm ? <X className="w-4 h-4" /> : <Plus className="w-4 h-4" />}
|
||||
{showForm ? 'Abbrechen' : 'Neuer Kunde'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Formular (Toggle) */}
|
||||
{showForm && (
|
||||
<div className="bg-white p-6 rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 animate-in fade-in slide-in-from-top-4 duration-200">
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-4">Kundendaten erfassen</h2>
|
||||
<form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Vorname *</label>
|
||||
<input type="text" required className="w-full border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" value={formData.firstName} onChange={e => setFormData({...formData, firstName: e.target.value})} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Nachname *</label>
|
||||
<input type="text" required className="w-full border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" value={formData.lastName} onChange={e => setFormData({...formData, lastName: e.target.value})} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">E-Mail *</label>
|
||||
<input type="email" required className="w-full border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" value={formData.email} onChange={e => setFormData({...formData, email: e.target.value})} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Telefon</label>
|
||||
<input type="tel" className="w-full border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" value={formData.phone} onChange={e => setFormData({...formData, phone: e.target.value})} />
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Firma (Optional)</label>
|
||||
<input type="text" className="w-full border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" value={formData.companyName} onChange={e => setFormData({...formData, companyName: e.target.value})} />
|
||||
</div>
|
||||
<div className="md:col-span-2 flex justify-end mt-2">
|
||||
<button type="submit" className="bg-slate-900 text-white px-6 py-2.5 rounded-lg hover:bg-slate-800 transition font-medium">
|
||||
Kunde speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Datentabelle */}
|
||||
<div className="bg-white rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 overflow-hidden">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="bg-slate-50 text-slate-600 font-medium border-b border-slate-200">
|
||||
<tr>
|
||||
<th className="py-4 px-6">ID</th>
|
||||
<th className="py-4 px-6">Kunde</th>
|
||||
<th className="py-4 px-6">Kontakt</th>
|
||||
<th className="py-4 px-6 text-right">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
{customers.map((c) => (
|
||||
<tr key={c.id} className="hover:bg-slate-50/80 transition-colors group border-b border-slate-50 last:border-0">
|
||||
<td className="py-4 px-6 font-mono text-slate-400">#{c.id.toString().padStart(4, '0')}</td>
|
||||
<td className="py-4 px-6">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-slate-900">{c.firstName} {c.lastName}</span>
|
||||
{c.companyName && (
|
||||
<span className="text-slate-500 text-xs flex items-center gap-1 mt-0.5">
|
||||
<Building2 className="w-3 h-3" /> {c.companyName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 px-6">
|
||||
<div className="flex flex-col gap-1 text-slate-600">
|
||||
<span className="flex items-center gap-2"><Mail className="w-4 h-4 text-slate-400" /> {c.email}</span>
|
||||
{c.phone && <span className="flex items-center gap-2 text-xs"><Phone className="w-3 h-3 text-slate-400" /> {c.phone}</span>}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 px-6 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<a href={`/customers/${c.id}`} className="text-indigo-600 font-medium hover:text-indigo-800 transition-opacity inline-block p-2">
|
||||
Akte öffnen
|
||||
</a>
|
||||
{canDelete && (
|
||||
<button onClick={() => handleDelete(c)} className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all" title="Kunde löschen">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{customers.length === 0 && (
|
||||
<tr><td colSpan={4} className="py-8 text-center text-slate-500">Keine Kunden vorhanden.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user