231 lines
10 KiB
TypeScript
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>
|
|
);
|
|
}
|