- currentMileage Feld zu Bike-Modell hinzugefügt - calculateServiceStatus verwendet jetzt aktuellen Kilometerstand des Fahrrads - Warnungen für überfällige Wartungen (OVERDUE Status) - Rote Warnung bei überfälligen Teilen - Kilometerstand wird in BikeDetail und BikeCard angezeigt - AlertBadge unterstützt jetzt critical Variante - Verbesserte Statusanzeige mit Überfällig-Hinweis
214 lines
7.8 KiB
TypeScript
214 lines
7.8 KiB
TypeScript
'use client'
|
||
|
||
import { WearPart } from '@prisma/client'
|
||
import { MaintenanceHistory } from '@prisma/client'
|
||
import { calculateServiceStatus } from '@/lib/utils'
|
||
import { formatDate, formatCurrency } from '@/lib/utils'
|
||
import AlertBadge from './AlertBadge'
|
||
import MaintenanceTimeline from './MaintenanceTimeline'
|
||
import { useState } from 'react'
|
||
|
||
interface WearPartListProps {
|
||
bikeId: string
|
||
parts: (WearPart & { maintenanceHistory: MaintenanceHistory[] })[]
|
||
bikeCurrentMileage?: number
|
||
onUpdate: () => void
|
||
}
|
||
|
||
export default function WearPartList({
|
||
bikeId,
|
||
parts,
|
||
bikeCurrentMileage,
|
||
onUpdate,
|
||
}: WearPartListProps) {
|
||
const [expandedPart, setExpandedPart] = useState<string | null>(null)
|
||
|
||
const handleDelete = async (partId: string) => {
|
||
if (!confirm('Möchten Sie dieses Verschleißteil wirklich löschen?')) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/parts/${partId}`, {
|
||
method: 'DELETE',
|
||
})
|
||
|
||
if (response.ok) {
|
||
onUpdate()
|
||
} else {
|
||
alert('Fehler beim Löschen des Verschleißteils')
|
||
}
|
||
} catch (error) {
|
||
console.error('Error deleting part:', error)
|
||
alert('Fehler beim Löschen des Verschleißteils')
|
||
}
|
||
}
|
||
|
||
if (parts.length === 0) {
|
||
return (
|
||
<div className="bg-white rounded-lg shadow-md p-8 text-center text-gray-500">
|
||
<p>Noch keine Verschleißteile erfasst.</p>
|
||
<p className="mt-2">Klicken Sie auf "Neues Verschleißteil" um zu beginnen.</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{parts.map((part) => {
|
||
const serviceStatus = calculateServiceStatus(part, bikeCurrentMileage)
|
||
const isExpanded = expandedPart === part.id
|
||
|
||
return (
|
||
<div
|
||
key={part.id}
|
||
className="bg-white rounded-lg shadow-md p-6"
|
||
>
|
||
<div className="flex justify-between items-start">
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<h3 className="text-xl font-semibold text-gray-900">
|
||
{part.type}
|
||
</h3>
|
||
{(serviceStatus.status !== 'OK' || serviceStatus.isOverdue) && (
|
||
<AlertBadge
|
||
count={1}
|
||
variant={serviceStatus.status === 'OVERDUE' ? 'critical' : 'warning'}
|
||
/>
|
||
)}
|
||
<span
|
||
className={`px-2 py-1 rounded text-xs font-medium ${
|
||
part.status === 'ACTIVE'
|
||
? 'bg-green-100 text-green-800'
|
||
: part.status === 'NEEDS_SERVICE'
|
||
? 'bg-orange-100 text-orange-800'
|
||
: part.status === 'REPLACED'
|
||
? 'bg-gray-100 text-gray-800'
|
||
: 'bg-red-100 text-red-800'
|
||
}`}
|
||
>
|
||
{part.status}
|
||
</span>
|
||
</div>
|
||
|
||
{part.brand && part.model && (
|
||
<p className="text-gray-600 mb-2">
|
||
{part.brand} {part.model}
|
||
</p>
|
||
)}
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm mt-4">
|
||
<div>
|
||
<span className="text-gray-500">Installiert:</span>
|
||
<p className="font-medium">{formatDate(part.installDate)}</p>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-500">Installations-KM:</span>
|
||
<p className="font-medium">{part.installMileage} km</p>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-500">Service-Intervall:</span>
|
||
<p className="font-medium">{part.serviceInterval} km</p>
|
||
</div>
|
||
{part.cost && (
|
||
<div>
|
||
<span className="text-gray-500">Kosten:</span>
|
||
<p className="font-medium">{formatCurrency(part.cost)}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-4">
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<span className="text-sm text-gray-500">Status:</span>
|
||
<span
|
||
className={`text-sm font-medium ${
|
||
serviceStatus.status === 'OK'
|
||
? 'text-green-600'
|
||
: serviceStatus.status === 'WARNING'
|
||
? 'text-orange-600'
|
||
: serviceStatus.status === 'CRITICAL'
|
||
? 'text-red-600'
|
||
: 'text-red-800 font-bold'
|
||
}`}
|
||
>
|
||
{serviceStatus.status === 'OK'
|
||
? 'OK'
|
||
: serviceStatus.status === 'WARNING'
|
||
? 'Warnung'
|
||
: serviceStatus.status === 'CRITICAL'
|
||
? 'Kritisch'
|
||
: 'Überfällig!'}
|
||
</span>
|
||
</div>
|
||
{serviceStatus.isOverdue && (
|
||
<div className="mb-2 p-2 bg-red-100 border border-red-300 rounded text-sm text-red-800">
|
||
⚠️ Wartung ist um {Math.abs(serviceStatus.remainingKm).toFixed(0)} km überfällig!
|
||
</div>
|
||
)}
|
||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||
<div
|
||
className={`h-2 rounded-full ${
|
||
serviceStatus.status === 'OK'
|
||
? 'bg-green-500'
|
||
: serviceStatus.status === 'WARNING'
|
||
? 'bg-orange-500'
|
||
: serviceStatus.status === 'CRITICAL'
|
||
? 'bg-red-500'
|
||
: 'bg-red-700'
|
||
}`}
|
||
style={{
|
||
width: `${Math.min(100, Math.max(0, serviceStatus.percentageUsed))}%`,
|
||
}}
|
||
/>
|
||
</div>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
{serviceStatus.isOverdue
|
||
? `${Math.abs(serviceStatus.remainingKm).toFixed(0)} km überfällig`
|
||
: `${serviceStatus.remainingKm.toFixed(0)} km bis zur Wartung`}
|
||
</p>
|
||
</div>
|
||
|
||
{part.notes && (
|
||
<div className="mt-4">
|
||
<span className="text-sm text-gray-500">Notizen:</span>
|
||
<p className="text-sm mt-1">{part.notes}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-2 ml-4">
|
||
<button
|
||
onClick={() =>
|
||
setExpandedPart(isExpanded ? null : part.id)
|
||
}
|
||
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors text-sm"
|
||
>
|
||
{isExpanded ? 'Weniger' : 'Historie'}
|
||
</button>
|
||
<button
|
||
onClick={() => handleDelete(part.id)}
|
||
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm"
|
||
>
|
||
Löschen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{isExpanded && (
|
||
<div className="mt-6 pt-6 border-t">
|
||
<MaintenanceTimeline
|
||
partId={part.id}
|
||
history={part.maintenanceHistory}
|
||
onUpdate={onUpdate}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
)
|
||
}
|
||
|