Files
WearPartTracker/app/components/MaintenanceTimeline.tsx
Denis Urs Rudolph de193bc783 Initial commit: Fahrrad Verschleißteile Tracker
- 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)
2025-12-05 22:17:50 +01:00

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>
)
}