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

547 lines
25 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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, FiArrowLeft, FiSave, FiEdit, FiX, FiTrash2, FiMapPin, FiCalendar } from 'react-icons/fi';
import Link from 'next/link';
import { formatDate } from '@/lib/date-utils';
export default function EquipmentDetailPage({ params }: { params: { id: string } }) {
const router = useRouter();
const { data: session } = useSession();
const [equipment, setEquipment] = useState<any>(null);
const [locations, setLocations] = useState<any[]>([]);
const [projects, setProjects] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [saving, setSaving] = useState(false);
const [formData, setFormData] = useState<any>(null);
useEffect(() => {
fetchEquipment();
fetchLocationsAndProjects();
}, [params.id]);
const fetchEquipment = async () => {
try {
const res = await fetch(`/api/inventory/${params.id}`);
if (res.ok) {
const data = await res.json();
setEquipment(data.equipment);
setFormData(data.equipment);
}
} catch (err) {
console.error('Error fetching equipment:', err);
} finally {
setLoading(false);
}
};
const fetchLocationsAndProjects = async () => {
try {
const [locRes, projRes] = await Promise.all([
fetch('/api/locations'),
fetch('/api/projects'),
]);
if (locRes.ok) {
const locData = await locRes.json();
setLocations(locData.locations || []);
}
if (projRes.ok) {
const projData = await projRes.json();
setProjects(projData.projects || []);
}
} catch (err) {
console.error('Error fetching data:', err);
}
};
const handleSave = async () => {
setSaving(true);
try {
const res = await fetch(`/api/inventory/${params.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...formData,
purchaseDate: formData.purchaseDate || null,
purchasePrice: formData.purchasePrice ? parseFloat(formData.purchasePrice) : null,
minStockLevel: formData.minStockLevel ? parseInt(formData.minStockLevel) : null,
currentStock: formData.currentStock ? parseInt(formData.currentStock) : null,
locationId: formData.locationId || null,
projectId: formData.projectId || null,
}),
});
if (res.ok) {
alert('Gespeichert!');
setEditing(false);
fetchEquipment();
} else {
alert('Fehler beim Speichern');
}
} catch (error) {
alert('Fehler beim Speichern');
} finally {
setSaving(false);
}
};
const handleDelete = async () => {
if (!confirm('Equipment wirklich löschen?')) return;
try {
const res = await fetch(`/api/inventory/${params.id}`, {
method: 'DELETE',
});
if (res.ok) {
alert('Equipment gelöscht!');
router.push('/dashboard/inventory');
} else {
alert('Fehler beim Löschen');
}
} catch (error) {
alert('Fehler beim Löschen');
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'AVAILABLE': return 'bg-green-500/20 text-green-400 border-green-500/50';
case 'IN_USE': return 'bg-blue-500/20 text-blue-400 border-blue-500/50';
case 'MAINTENANCE': return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50';
case 'DAMAGED': return 'bg-red-500/20 text-red-400 border-red-500/50';
case 'RESERVED': return 'bg-purple-500/20 text-purple-400 border-purple-500/50';
default: return 'bg-gray-500/20 text-gray-400 border-gray-500/50';
}
};
const getStatusLabel = (status: string) => {
const labels: any = {
AVAILABLE: 'Verfügbar',
IN_USE: 'Im Einsatz',
MAINTENANCE: 'Wartung',
DAMAGED: 'Beschädigt',
RESERVED: 'Reserviert',
};
return labels[status] || status;
};
const getTypeLabel = (type: string) => {
const labels: any = {
PRINTER: 'Drucker',
CARPET: 'Roter Teppich',
VIP_BARRIER: 'VIP Absperrband',
ACCESSORIES_KIT: 'Accessoires-Koffer',
PRINTER_PAPER: 'Druckerpapier',
TRIPOD: 'Stativ',
OTHER: 'Sonstiges',
};
return labels[type] || type;
};
if (!session) {
return null;
}
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 flex items-center justify-center">
<div className="text-white text-xl">Lade...</div>
</div>
);
}
if (!equipment) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 flex items-center justify-center">
<div className="text-gray-400 text-xl">Equipment nicht gefunden</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-6xl mx-auto">
<div className="flex items-center justify-between mb-8">
<div>
<Link href="/dashboard/inventory" className="text-sm text-gray-400 hover:text-gray-300 mb-2 inline-flex items-center gap-2 transition-colors">
<FiArrowLeft /> Zurück zum Inventar
</Link>
<h2 className="text-3xl font-bold text-white mt-2">{equipment.name}</h2>
<p className="text-gray-400 mt-1">{getTypeLabel(equipment.type)}</p>
</div>
<div className="flex gap-3">
{!editing ? (
<>
<button
onClick={() => setEditing(true)}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-gray-600 to-gray-700 text-white rounded-lg hover:from-gray-700 hover:to-gray-800 shadow-lg transition-all"
>
<FiEdit /> Bearbeiten
</button>
<button
onClick={handleDelete}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-red-600 to-pink-600 text-white rounded-lg hover:from-red-700 hover:to-pink-700 shadow-lg transition-all"
>
<FiTrash2 /> Löschen
</button>
</>
) : (
<>
<button
onClick={() => {
setEditing(false);
setFormData(equipment);
}}
className="flex items-center gap-2 px-4 py-2 bg-gray-700 text-gray-300 rounded-lg hover:bg-gray-600 transition-colors"
>
<FiX /> Abbrechen
</button>
<button
onClick={handleSave}
disabled={saving}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-red-600 to-pink-600 text-white rounded-lg hover:from-red-700 hover:to-pink-700 disabled:opacity-50 shadow-lg transition-all"
>
<FiSave /> {saving ? 'Speichern...' : 'Speichern'}
</button>
</>
)}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold text-white">Status</h3>
<div className={`px-4 py-2 border-2 rounded-lg font-semibold ${getStatusColor(equipment.status)}`}>
{getStatusLabel(equipment.status)}
</div>
</div>
{editing && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Status ändern</label>
<select
value={formData.status}
onChange={(e) => setFormData({ ...formData, status: e.target.value })}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
>
<option value="AVAILABLE">Verfügbar</option>
<option value="IN_USE">Im Einsatz</option>
<option value="MAINTENANCE">Wartung</option>
<option value="DAMAGED">Beschädigt</option>
<option value="RESERVED">Reserviert</option>
</select>
</div>
)}
</div>
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Details</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Name/Bezeichnung</label>
{editing ? (
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.name}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Typ</label>
{editing ? (
<select
value={formData.type}
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
>
<option value="PRINTER">Drucker</option>
<option value="CARPET">Roter Teppich</option>
<option value="VIP_BARRIER">VIP Absperrband</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>
) : (
<p className="text-white">{getTypeLabel(equipment.type)}</p>
)}
</div>
{equipment.brand && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Hersteller</label>
{editing ? (
<input
type="text"
value={formData.brand || ''}
onChange={(e) => setFormData({ ...formData, brand: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.brand}</p>
)}
</div>
)}
{equipment.model && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Modell</label>
{editing ? (
<input
type="text"
value={formData.model || ''}
onChange={(e) => setFormData({ ...formData, model: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.model}</p>
)}
</div>
)}
{equipment.serialNumber && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Seriennummer</label>
{editing ? (
<input
type="text"
value={formData.serialNumber || ''}
onChange={(e) => setFormData({ ...formData, serialNumber: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.serialNumber}</p>
)}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Anzahl</label>
{editing ? (
<input
type="number"
value={formData.quantity}
onChange={(e) => setFormData({ ...formData, quantity: parseInt(e.target.value) })}
min="1"
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.quantity}</p>
)}
</div>
</div>
</div>
{(equipment.currentStock !== null || equipment.minStockLevel !== null || editing) && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Bestandsverwaltung</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Aktueller Bestand</label>
{editing ? (
<input
type="number"
value={formData.currentStock || ''}
onChange={(e) => setFormData({ ...formData, currentStock: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.currentStock || '-'}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Mindestbestand</label>
{editing ? (
<input
type="number"
value={formData.minStockLevel || ''}
onChange={(e) => setFormData({ ...formData, minStockLevel: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-white">{equipment.minStockLevel || '-'}</p>
)}
</div>
</div>
{equipment.currentStock !== null && equipment.minStockLevel !== null && equipment.currentStock < equipment.minStockLevel && (
<div className="mt-4 bg-yellow-500/20 border border-yellow-500/50 text-yellow-400 px-4 py-3 rounded-lg">
Niedriger Bestand! Nachbestellen erforderlich.
</div>
)}
</div>
)}
{equipment.notes && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Notizen</h3>
{editing ? (
<textarea
value={formData.notes || ''}
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
rows={4}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : (
<p className="text-gray-300 whitespace-pre-wrap">{equipment.notes}</p>
)}
</div>
)}
{equipment.bookingEquipment && equipment.bookingEquipment.length > 0 && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiCalendar /> Buchungen
</h3>
<div className="space-y-3">
{equipment.bookingEquipment.map((be: any) => (
<Link
key={be.id}
href={`/dashboard/bookings/${be.booking.id}`}
className="block p-4 bg-gray-700/50 border border-gray-600 rounded-lg hover:bg-gray-700 hover:border-gray-500 transition-all"
>
<div className="flex items-center justify-between">
<div>
<p className="font-semibold text-white">{be.booking.customerName}</p>
<p className="text-sm text-gray-400">{be.booking.eventCity}</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-400">{formatDate(be.booking.eventDate)}</p>
<p className="text-xs text-gray-500">Menge: {be.quantity}</p>
</div>
</div>
</Link>
))}
</div>
</div>
)}
</div>
<div className="space-y-6">
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiMapPin /> Standort
</h3>
{editing ? (
<select
value={formData.locationId || ''}
onChange={(e) => setFormData({ ...formData, locationId: e.target.value })}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
>
<option value="">Kein Standort</option>
{locations.map((loc) => (
<option key={loc.id} value={loc.id}>{loc.name}</option>
))}
</select>
) : equipment.location ? (
<div>
<p className="text-white font-semibold">{equipment.location.name}</p>
<p className="text-sm text-gray-400">{equipment.location.city}</p>
</div>
) : (
<p className="text-gray-400">Kein Standort zugewiesen</p>
)}
</div>
{(equipment.project || editing) && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
<FiPackage /> Projekt
</h3>
{editing ? (
<select
value={formData.projectId || ''}
onChange={(e) => setFormData({ ...formData, projectId: e.target.value })}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent"
>
<option value="">Kein Projekt</option>
{projects.map((proj) => (
<option key={proj.id} value={proj.id}>{proj.name}</option>
))}
</select>
) : equipment.project ? (
<div>
<p className="text-white font-semibold">{equipment.project.name}</p>
{equipment.project.description && (
<p className="text-sm text-gray-400 mt-2">{equipment.project.description}</p>
)}
</div>
) : (
<p className="text-gray-400">Kein Projekt zugewiesen</p>
)}
</div>
)}
{(equipment.purchaseDate || equipment.purchasePrice || editing) && (
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Kaufinformationen</h3>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Kaufdatum</label>
{editing ? (
<input
type="date"
value={formData.purchaseDate ? new Date(formData.purchaseDate).toISOString().split('T')[0] : ''}
onChange={(e) => setFormData({ ...formData, purchaseDate: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : equipment.purchaseDate ? (
<p className="text-white">{formatDate(equipment.purchaseDate)}</p>
) : (
<p className="text-gray-400">-</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">Kaufpreis</label>
{editing ? (
<input
type="number"
step="0.01"
value={formData.purchasePrice || ''}
onChange={(e) => setFormData({ ...formData, purchasePrice: e.target.value })}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white rounded-lg focus:ring-2 focus:ring-red-500"
/>
) : equipment.purchasePrice ? (
<p className="text-white text-2xl font-bold">{equipment.purchasePrice}</p>
) : (
<p className="text-gray-400">-</p>
)}
</div>
</div>
</div>
)}
<div className="bg-gradient-to-br from-gray-800 to-gray-900 border border-gray-700 rounded-xl shadow-sm p-6">
<h3 className="text-xl font-bold text-white mb-4">Erstellt</h3>
<p className="text-gray-400">{formatDate(equipment.createdAt)}</p>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
);
}