Files
Atlas/app/dashboard/inventory/page.tsx
2025-11-12 20:21:32 +01:00

231 lines
10 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import DashboardSidebar from '@/components/DashboardSidebar';
import { useSession } from 'next-auth/react';
import { FiPackage, FiAlertCircle, FiPlus } from 'react-icons/fi';
export default function InventoryPage() {
const router = useRouter();
const { data: session } = useSession();
const [equipment, setEquipment] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [typeFilter, setTypeFilter] = useState('ALL');
const [statusFilter, setStatusFilter] = useState('ALL');
const [locationFilter, setLocationFilter] = useState('ALL');
useEffect(() => {
fetchEquipment();
}, []);
const fetchEquipment = async () => {
try {
const res = await fetch('/api/inventory');
const data = await res.json();
setEquipment(data.equipment || []);
} catch (error) {
console.error('Fetch error:', error);
} finally {
setLoading(false);
}
};
const filteredEquipment = equipment.filter((item) => {
if (typeFilter !== 'ALL' && item.type !== typeFilter) return false;
if (statusFilter !== 'ALL' && item.status !== statusFilter) return false;
if (locationFilter !== 'ALL' && item.locationId !== locationFilter) return false;
return true;
});
const getTypeLabel = (type: string) => {
const labels: Record<string, string> = {
PRINTER: 'Drucker',
CARPET: 'Roter Teppich',
VIP_BARRIER: 'VIP-Absperrung',
ACCESSORIES_KIT: 'Accessoires-Koffer',
PRINTER_PAPER: 'Druckerpapier',
TRIPOD: 'Stativ',
OTHER: 'Sonstiges',
};
return labels[type] || type;
};
const getStatusBadge = (status: string) => {
const styles: Record<string, string> = {
AVAILABLE: 'bg-green-500/20 text-green-400 border-green-500/50',
IN_USE: 'bg-blue-500/20 text-blue-400 border-blue-500/50',
MAINTENANCE: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50',
DAMAGED: 'bg-red-500/20 text-red-400 border-red-500/50',
RESERVED: 'bg-purple-500/20 text-purple-400 border-purple-500/50',
};
return styles[status] || 'bg-gray-500/20 text-gray-400 border-gray-500/50';
};
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
AVAILABLE: 'Verfügbar',
IN_USE: 'Im Einsatz',
MAINTENANCE: 'Wartung',
DAMAGED: 'Defekt',
RESERVED: 'Reserviert',
};
return labels[status] || status;
};
const stats = {
total: equipment.length,
available: equipment.filter((e) => e.status === 'AVAILABLE').length,
inUse: equipment.filter((e) => e.status === 'IN_USE').length,
lowStock: equipment.filter((e) => e.currentStock && e.minStockLevel && e.currentStock < e.minStockLevel).length,
};
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
<div className="flex">
<DashboardSidebar user={session?.user} />
<div className="flex-1 p-8">
<p className="text-gray-300">Lädt...</p>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
<div className="flex">
<DashboardSidebar user={session?.user} />
<main className="flex-1 p-8">
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-purple-400 to-pink-500 bg-clip-text text-transparent">
Inventar
</h1>
<p className="text-gray-400 mt-1">Verwalten Sie Drucker, Zubehör & Verbrauchsmaterial</p>
</div>
<button
onClick={() => router.push('/dashboard/inventory/new')}
className="px-6 py-3 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-lg hover:from-purple-700 hover:to-pink-700 font-semibold shadow-lg flex items-center gap-2"
>
<FiPlus /> Neues Equipment
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 p-6 rounded-xl">
<p className="text-sm text-gray-400">Gesamt</p>
<p className="text-3xl font-bold text-white mt-2">{stats.total}</p>
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 p-6 rounded-xl">
<p className="text-sm text-gray-400">Verfügbar</p>
<p className="text-3xl font-bold text-green-400 mt-2">{stats.available}</p>
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 p-6 rounded-xl">
<p className="text-sm text-gray-400">Im Einsatz</p>
<p className="text-3xl font-bold text-blue-400 mt-2">{stats.inUse}</p>
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 p-6 rounded-xl">
<p className="text-sm text-gray-400">Niedriger Bestand</p>
<p className="text-3xl font-bold text-yellow-400 mt-2">{stats.lowStock}</p>
</div>
</div>
{/* Filters */}
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl p-6 mb-6">
<div className="flex gap-4">
<select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
className="px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-purple-500"
>
<option value="ALL">Alle Typen</option>
<option value="PRINTER">Drucker</option>
<option value="CARPET">Roter Teppich</option>
<option value="VIP_BARRIER">VIP-Absperrung</option>
<option value="ACCESSORIES_KIT">Accessoires-Koffer</option>
<option value="PRINTER_PAPER">Druckerpapier</option>
<option value="TRIPOD">Stativ</option>
<option value="OTHER">Sonstiges</option>
</select>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-purple-500"
>
<option value="ALL">Alle Status</option>
<option value="AVAILABLE">Verfügbar</option>
<option value="IN_USE">Im Einsatz</option>
<option value="MAINTENANCE">Wartung</option>
<option value="DAMAGED">Defekt</option>
<option value="RESERVED">Reserviert</option>
</select>
</div>
</div>
{/* Equipment Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEquipment.map((item) => (
<div
key={item.id}
onClick={() => router.push(`/dashboard/inventory/${item.id}`)}
className="bg-gradient-to-br from-gray-800 to-gray-900 rounded-lg shadow-xl p-6 hover:shadow-2xl transition-all cursor-pointer border border-gray-700 hover:border-purple-500"
>
<div className="flex justify-between items-start mb-4">
<div>
<h3 className="text-lg font-bold text-white">{item.name}</h3>
<p className="text-sm text-gray-400">{getTypeLabel(item.type)}</p>
{item.brand && item.model && (
<p className="text-xs text-gray-500 mt-1">{item.brand} {item.model}</p>
)}
</div>
<span className={`px-3 py-1 text-xs font-semibold rounded-full border ${getStatusBadge(item.status)}`}>
{getStatusLabel(item.status)}
</span>
</div>
<div className="text-sm text-gray-400 space-y-2">
{item.location && (
<p><span className="font-medium text-gray-300">Standort:</span> {item.location.name}</p>
)}
{item.project && (
<p><span className="font-medium text-gray-300">Projekt:</span> {item.project.name}</p>
)}
{item.quantity > 1 && (
<p><span className="font-medium text-gray-300">Menge:</span> {item.quantity}x</p>
)}
{item.serialNumber && (
<p><span className="font-medium text-gray-300">SN:</span> {item.serialNumber}</p>
)}
{item.currentStock !== null && item.minStockLevel !== null && (
<div className="pt-2 border-t border-gray-700">
<p className="font-medium text-gray-300 flex items-center gap-2">
Bestand: {item.currentStock} / {item.minStockLevel}
{item.currentStock < item.minStockLevel && (
<FiAlertCircle className="text-yellow-400" />
)}
</p>
</div>
)}
</div>
</div>
))}
</div>
{filteredEquipment.length === 0 && (
<div className="text-center py-12">
<FiPackage className="text-6xl text-gray-600 mx-auto mb-4" />
<p className="text-gray-400">Kein Equipment gefunden</p>
</div>
)}
</div>
</main>
</div>
</div>
);
}