- 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)
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import { BikeWithParts } from '@/types'
|
|
import { useState } from 'react'
|
|
import Link from 'next/link'
|
|
import WearPartList from './WearPartList'
|
|
import WearPartForm from './WearPartForm'
|
|
|
|
interface BikeDetailProps {
|
|
bike: BikeWithParts
|
|
onUpdate: () => void
|
|
}
|
|
|
|
export default function BikeDetail({ bike, onUpdate }: BikeDetailProps) {
|
|
const [showPartForm, setShowPartForm] = useState(false)
|
|
|
|
return (
|
|
<main className="min-h-screen p-8 bg-gray-50">
|
|
<div className="max-w-7xl mx-auto">
|
|
<Link
|
|
href="/"
|
|
className="inline-flex items-center text-blue-600 hover:text-blue-800 mb-6"
|
|
>
|
|
← Zurück zur Übersicht
|
|
</Link>
|
|
|
|
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-4">{bike.name}</h1>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
{bike.brand && (
|
|
<div>
|
|
<span className="text-gray-500">Marke:</span>
|
|
<p className="font-medium">{bike.brand}</p>
|
|
</div>
|
|
)}
|
|
{bike.model && (
|
|
<div>
|
|
<span className="text-gray-500">Modell:</span>
|
|
<p className="font-medium">{bike.model}</p>
|
|
</div>
|
|
)}
|
|
{bike.purchaseDate && (
|
|
<div>
|
|
<span className="text-gray-500">Kaufdatum:</span>
|
|
<p className="font-medium">
|
|
{new Date(bike.purchaseDate).toLocaleDateString('de-DE')}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{bike.notes && (
|
|
<div className="mt-4">
|
|
<span className="text-gray-500">Notizen:</span>
|
|
<p className="mt-1">{bike.notes}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-6 flex justify-between items-center">
|
|
<h2 className="text-2xl font-semibold text-gray-900">
|
|
Verschleißteile
|
|
</h2>
|
|
<button
|
|
onClick={() => setShowPartForm(!showPartForm)}
|
|
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
|
>
|
|
{showPartForm ? 'Abbrechen' : '+ Neues Verschleißteil'}
|
|
</button>
|
|
</div>
|
|
|
|
{showPartForm && (
|
|
<div className="mb-6">
|
|
<WearPartForm
|
|
bikeId={bike.id}
|
|
onSuccess={() => {
|
|
setShowPartForm(false)
|
|
onUpdate()
|
|
}}
|
|
onCancel={() => setShowPartForm(false)}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<WearPartList bikeId={bike.id} parts={bike.wearParts} onUpdate={onUpdate} />
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|