Files
2026-05-20 18:58:23 +00:00

238 lines
11 KiB
TypeScript
Raw Permalink 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.
// /opt/erp-system/app/page.tsx
'use client';
import { useState, useEffect } from 'react';
import { useSession } from "next-auth/react";
import { Ticket as TicketIcon, Clock, Activity, ArrowRight, Plus, X, CheckCircle2 } from 'lucide-react';
import Link from 'next/link';
import { getStatusBadge } from './components/AppShell';
export default function DashboardPage() {
const { data: session } = useSession();
const [data, setData] = useState<any>(null);
const [error, setError] = useState(false);
const [showForm, setShowForm] = useState(false);
const [formData, setFormData] = useState({ title: '', description: '' });
useEffect(() => {
fetchData();
}, []);
const fetchData = () => {
fetch('/api/dashboard')
.then(res => {
if (!res.ok) throw new Error('Netzwerkfehler');
return res.json();
})
.then(setData)
.catch(() => setError(true));
};
const handleCustomerTicketSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch('/api/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: formData.title,
description: formData.description,
customerId: (session?.user as any)?.id
})
});
if (res.ok) {
setShowForm(false);
setFormData({ title: '', description: '' });
fetchData();
} else {
alert('Fehler beim Erstellen des Tickets');
}
};
if (error) return <div className="p-8 text-red-600 font-medium">Fehler beim Laden des Dashboards.</div>;
if (!data) return <div className="p-8 text-slate-500 font-medium animate-pulse">Lade Metriken...</div>;
const userType = (session?.user as any)?.userType;
// ----------------------------------------------------
// INTERFACE FÜR KUNDEN
// ----------------------------------------------------
if (userType === 'CUSTOMER') {
return (
<div className="space-y-8 animate-fade-in-up stagger-1">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900">
Hallo, {(session?.user as any)?.firstName}!
</h1>
<p className="text-slate-500 mt-1">Willkommen in deinem Service-Portal. Hier kannst du deine Support-Anfragen verwalten.</p>
</div>
<button
onClick={() => setShowForm(!showForm)}
className="bg-indigo-600 text-white px-5 py-2.5 rounded-lg font-medium shadow-sm flex items-center gap-2 hover:bg-indigo-700 transition self-start md:self-auto"
>
{showForm ? <X className="w-4 h-4" /> : <Plus className="w-4 h-4" />}
{showForm ? 'Abbrechen' : 'Neues Ticket eröffnen'}
</button>
</div>
{showForm && (
<div className="bg-white p-6 rounded-xl shadow-sm border border-slate-200 animate-in fade-in slide-in-from-top-4">
<h2 className="text-lg font-semibold text-slate-800 mb-4">Unterstützung anfordern</h2>
<form onSubmit={handleCustomerTicketSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Betreff / Thema *</label>
<input type="text" required className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" value={formData.title} onChange={e => setFormData({...formData, title: e.target.value})} placeholder="Wobei benötigst du Hilfe?" />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Beschreibung des Problems *</label>
<textarea required className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none h-32 resize-none" value={formData.description} onChange={e => setFormData({...formData, description: e.target.value})} placeholder="Bitte beschreibe das Problem so genau wie möglich..." />
</div>
<div className="flex justify-end">
<button type="submit" className="bg-slate-900 text-white px-6 py-2.5 rounded-lg hover:bg-slate-800 transition font-medium">Ticket absenden</button>
</div>
</form>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 flex items-start gap-4 hover-lift">
<div className="p-3 bg-amber-50 text-amber-600 rounded-lg">
<TicketIcon className="w-6 h-6" />
</div>
<div>
<p className="text-sm font-medium text-slate-500">In Bearbeitung / Offen</p>
<p className="text-2xl font-bold text-slate-900">{data.openTickets}</p>
</div>
</div>
<div className="bg-white p-6 rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 flex items-start gap-4 hover-lift">
<div className="p-3 bg-emerald-50 text-emerald-600 rounded-lg">
<CheckCircle2 className="w-6 h-6" />
</div>
<div>
<p className="text-sm font-medium text-slate-500">Gelöste Tickets</p>
<p className="text-2xl font-bold text-slate-900">{data.closedTickets}</p>
</div>
</div>
</div>
<div className="bg-white rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 overflow-hidden animate-fade-in-up stagger-2">
<div className="p-6 border-b border-slate-200">
<h2 className="text-lg font-semibold text-slate-800">Deine Tickets</h2>
</div>
<div className="divide-y divide-slate-100">
{data.recentTickets.length > 0 ? data.recentTickets.map((ticket: any) => (
<div key={ticket.id} className="p-4 px-6 flex items-center justify-between hover:bg-slate-50 transition group">
<div>
<Link href={`/tickets/${ticket.id}`} className="font-semibold text-slate-900 group-hover:text-indigo-600 transition">
#{ticket.id} {ticket.title}
</Link>
<p className="text-sm text-slate-500 mt-0.5">
Erstellt am: {new Date(ticket.createdAt).toLocaleDateString('de-DE')} Letztes Update: {new Date(ticket.updatedAt).toLocaleString('de-DE')}
</p>
</div>
<div>
{getStatusBadge(ticket.status)}
</div>
</div>
)) : (
<div className="p-8 text-center text-slate-500">Du hast bisher noch keine Tickets erstellt.</div>
)}
</div>
</div>
</div>
);
}
// ----------------------------------------------------
// INTERFACE FÜR TEAM
// ----------------------------------------------------
return (
<div className="max-w-7xl mx-auto space-y-8 animate-fade-in-up stagger-1">
<div>
<h1 className="text-2xl font-bold text-slate-900">
Willkommen zurück, {(session?.user as any)?.firstName}!
</h1>
<p className="text-slate-500 mt-1">Hier ist der aktuelle Status deines ERP-Systems.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-6 rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 flex items-start gap-4 hover-lift">
<div className="p-3 bg-red-50 text-red-600 rounded-lg">
<TicketIcon className="w-6 h-6" />
</div>
<div>
<p className="text-sm font-medium text-slate-500">Offene Tickets (Gesamt)</p>
<p className="text-3xl font-extrabold text-slate-900 mt-1">{data.openTickets}</p>
</div>
<div className="ml-auto flex items-end gap-1 h-12">
{[40, 70, 45, 90, 65, 80].map((h, i) => (
<div key={i} className="w-1.5 bg-red-200 rounded-t-sm animate-grow-bar" style={{ height: `${h}%`, animationDelay: `${i * 100}ms` }}></div>
))}
</div>
</div>
<div className="bg-white p-6 rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 flex items-start gap-4 hover-lift">
<div className="p-3 bg-indigo-50 text-indigo-600 rounded-lg">
<Activity className="w-6 h-6" />
</div>
<div>
<p className="text-sm font-medium text-slate-500">Dir zugewiesen (Offen)</p>
<p className="text-3xl font-extrabold text-slate-900 mt-1">{data.myTickets}</p>
</div>
<div className="ml-auto flex items-end gap-1 h-12">
{[20, 40, 30, 60, 50, 70].map((h, i) => (
<div key={i} className="w-1.5 bg-indigo-200 rounded-t-sm animate-grow-bar" style={{ height: `${h}%`, animationDelay: `${i * 100}ms` }}></div>
))}
</div>
</div>
<div className="bg-white p-6 rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 flex items-start gap-4 hover-lift">
<div className="p-3 bg-emerald-50 text-emerald-600 rounded-lg">
<Clock className="w-6 h-6" />
</div>
<div>
<p className="text-sm font-medium text-slate-500">Erfasste Stunden (Gesamt)</p>
<p className="text-3xl font-extrabold text-slate-900 mt-1">{data.totalHours.toFixed(1)} <span className="text-lg text-slate-400 font-medium">h</span></p>
</div>
<div className="ml-auto flex items-end gap-1 h-12">
{[30, 50, 40, 80, 60, 90].map((h, i) => (
<div key={i} className="w-1.5 bg-emerald-200 rounded-t-sm animate-grow-bar" style={{ height: `${h}%`, animationDelay: `${i * 100}ms` }}></div>
))}
</div>
</div>
</div>
<div className="bg-white rounded-2xl shadow-lg shadow-slate-200/50 border border-slate-100 overflow-hidden animate-fade-in-up stagger-2">
<div className="p-6 border-b border-slate-200 flex items-center justify-between">
<h2 className="text-lg font-semibold text-slate-800">Zuletzt aktualisierte Tickets</h2>
<Link href="/tickets" className="text-sm font-medium text-indigo-600 hover:text-indigo-800 flex items-center gap-1">
Alle ansehen <ArrowRight className="w-4 h-4" />
</Link>
</div>
<div className="divide-y divide-slate-100">
{data.recentTickets.length > 0 ? data.recentTickets.map((ticket: any) => (
<div key={ticket.id} className="p-4 px-6 flex items-center justify-between hover:bg-slate-50 transition group">
<div>
<Link href={`/tickets/${ticket.id}`} className="font-semibold text-slate-900 group-hover:text-indigo-600 transition">
#{ticket.id} {ticket.title}
</Link>
<p className="text-sm text-slate-500 mt-0.5">
{ticket.customer.companyName || `${ticket.customer.firstName} ${ticket.customer.lastName}`} Update: {new Date(ticket.updatedAt).toLocaleString('de-DE')}
</p>
</div>
<div>
{getStatusBadge(ticket.status)}
</div>
</div>
)) : (
<div className="p-8 text-center text-slate-500">Keine Tickets vorhanden.</div>
)}
</div>
</div>
</div>
);
}