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)
This commit is contained in:
93
app/components/MaintenanceTimeline.tsx
Normal file
93
app/components/MaintenanceTimeline.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user