- Next.js SPA mit Bun Runtime - Prisma mit SQLite Datenbank - Vollständige CRUD-Operationen für Fahrräder, Verschleißteile und Wartungshistorie - Warnsystem für bevorstehende Wartungen - Statistik-Features (Gesamtkosten, durchschnittliche Lebensdauer) - Zod-Validierung für alle API-Requests - Umfassende Test-Suite (41 Tests)
94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
'use client'
|
|
|
|
import { MaintenanceHistory } from '@prisma/client'
|
|
import { formatDate, formatCurrency } from '@/lib/utils'
|
|
import { useState } from 'react'
|
|
import MaintenanceForm from './MaintenanceForm'
|
|
|
|
interface MaintenanceTimelineProps {
|
|
partId: string
|
|
history: MaintenanceHistory[]
|
|
onUpdate: () => void
|
|
}
|
|
|
|
const actionLabels: Record<string, string> = {
|
|
INSTALL: 'Installiert',
|
|
REPLACE: 'Ersetzt',
|
|
SERVICE: 'Gewartet',
|
|
CHECK: 'Geprüft',
|
|
ADJUST: 'Eingestellt',
|
|
}
|
|
|
|
export default function MaintenanceTimeline({
|
|
partId,
|
|
history,
|
|
onUpdate,
|
|
}: MaintenanceTimelineProps) {
|
|
const [showForm, setShowForm] = useState(false)
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-lg font-semibold text-gray-900">
|
|
Wartungshistorie
|
|
</h3>
|
|
<button
|
|
onClick={() => setShowForm(!showForm)}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm"
|
|
>
|
|
{showForm ? 'Abbrechen' : '+ Neuer Eintrag'}
|
|
</button>
|
|
</div>
|
|
|
|
{showForm && (
|
|
<div className="mb-4">
|
|
<MaintenanceForm
|
|
partId={partId}
|
|
onSuccess={() => {
|
|
setShowForm(false)
|
|
onUpdate()
|
|
}}
|
|
onCancel={() => setShowForm(false)}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{history.length === 0 ? (
|
|
<p className="text-gray-500 text-sm">Noch keine Wartungseinträge.</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{history.map((entry) => (
|
|
<div
|
|
key={entry.id}
|
|
className="flex items-start gap-4 p-4 bg-gray-50 rounded-lg"
|
|
>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="font-semibold text-gray-900">
|
|
{actionLabels[entry.action] || entry.action}
|
|
</span>
|
|
<span className="text-sm text-gray-500">
|
|
{formatDate(entry.date)}
|
|
</span>
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
<span>Kilometerstand: {entry.mileage} km</span>
|
|
{entry.cost && (
|
|
<span className="ml-4">
|
|
Kosten: {formatCurrency(entry.cost)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{entry.notes && (
|
|
<p className="text-sm text-gray-600 mt-2">{entry.notes}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|