238 lines
11 KiB
TypeScript
238 lines
11 KiB
TypeScript
// /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>
|
||
);
|
||
}
|