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:
92
app/components/BikeCard.tsx
Normal file
92
app/components/BikeCard.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
'use client'
|
||||
|
||||
import { BikeWithParts } from '@/types'
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { calculateServiceStatus } from '@/lib/utils'
|
||||
import AlertBadge from './AlertBadge'
|
||||
|
||||
interface BikeCardProps {
|
||||
bike: BikeWithParts
|
||||
onDelete: () => void
|
||||
}
|
||||
|
||||
export default function BikeCard({ bike, onDelete }: BikeCardProps) {
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!confirm('Möchten Sie dieses Fahrrad wirklich löschen?')) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsDeleting(true)
|
||||
try {
|
||||
const response = await fetch(`/api/bikes/${bike.id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
onDelete()
|
||||
} else {
|
||||
alert('Fehler beim Löschen des Fahrrads')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting bike:', error)
|
||||
alert('Fehler beim Löschen des Fahrrads')
|
||||
} finally {
|
||||
setIsDeleting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const activeParts = bike.wearParts.filter((p) => p.status === 'ACTIVE')
|
||||
const needsServiceParts = activeParts.filter((part) => {
|
||||
const serviceStatus = calculateServiceStatus(part)
|
||||
return serviceStatus.status !== 'OK'
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-gray-900">{bike.name}</h2>
|
||||
{bike.brand && bike.model && (
|
||||
<p className="text-gray-600 mt-1">
|
||||
{bike.brand} {bike.model}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{needsServiceParts.length > 0 && (
|
||||
<AlertBadge count={needsServiceParts.length} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
{activeParts.length} aktive Verschleißteile
|
||||
</p>
|
||||
{needsServiceParts.length > 0 && (
|
||||
<p className="text-sm text-orange-600 font-medium mt-1">
|
||||
{needsServiceParts.length} benötigen Wartung
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Link
|
||||
href={`/bikes/${bike.id}`}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white text-center rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Details
|
||||
</Link>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isDeleting ? 'Löschen...' : 'Löschen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user