2025-12-05 22:17:50 +01:00
|
|
|
import { z } from 'zod'
|
|
|
|
|
|
|
|
|
|
export const WearPartType = z.enum([
|
|
|
|
|
'CHAIN',
|
|
|
|
|
'BRAKE_PADS',
|
|
|
|
|
'TIRE',
|
|
|
|
|
'CASSETTE',
|
|
|
|
|
'CHAINRING',
|
|
|
|
|
'DERAILLEUR',
|
|
|
|
|
'BRAKE_CABLE',
|
|
|
|
|
'SHIFT_CABLE',
|
|
|
|
|
'BRAKE_ROTOR',
|
|
|
|
|
'PEDAL',
|
|
|
|
|
'CRANKSET',
|
|
|
|
|
'BOTTOM_BRACKET',
|
|
|
|
|
'HEADSET',
|
|
|
|
|
'WHEEL',
|
|
|
|
|
'HUB',
|
|
|
|
|
'SPOKE',
|
|
|
|
|
'OTHER',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
export const WearPartStatus = z.enum([
|
|
|
|
|
'ACTIVE',
|
|
|
|
|
'NEEDS_SERVICE',
|
|
|
|
|
'REPLACED',
|
|
|
|
|
'INACTIVE',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
export const MaintenanceAction = z.enum([
|
|
|
|
|
'INSTALL',
|
|
|
|
|
'REPLACE',
|
|
|
|
|
'SERVICE',
|
|
|
|
|
'CHECK',
|
|
|
|
|
'ADJUST',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
export const bikeSchema = z.object({
|
2025-12-05 22:18:25 +01:00
|
|
|
name: z
|
|
|
|
|
.string()
|
|
|
|
|
.min(1, 'Name ist erforderlich')
|
|
|
|
|
.trim()
|
|
|
|
|
.min(1, 'Name darf nicht nur aus Leerzeichen bestehen'),
|
|
|
|
|
brand: z.string().optional().transform((val) => val?.trim() || undefined),
|
|
|
|
|
model: z.string().optional().transform((val) => val?.trim() || undefined),
|
2025-12-05 22:25:07 +01:00
|
|
|
purchaseDate: z
|
|
|
|
|
.union([z.date(), z.string()])
|
|
|
|
|
.optional()
|
|
|
|
|
.transform((val) => {
|
|
|
|
|
if (!val || val === undefined) return undefined
|
|
|
|
|
if (val instanceof Date) {
|
|
|
|
|
return isNaN(val.getTime()) ? undefined : val
|
|
|
|
|
}
|
|
|
|
|
if (typeof val === 'string') {
|
|
|
|
|
// Handle YYYY-MM-DD format (from HTML date input)
|
|
|
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(val)) {
|
|
|
|
|
const date = new Date(val + 'T00:00:00.000Z')
|
|
|
|
|
return isNaN(date.getTime()) ? undefined : date
|
|
|
|
|
}
|
|
|
|
|
// Handle ISO datetime format or any other parseable date string
|
|
|
|
|
const date = new Date(val)
|
|
|
|
|
return isNaN(date.getTime()) ? undefined : date
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
})
|
|
|
|
|
.refine((val) => val === undefined || val instanceof Date, {
|
|
|
|
|
message: 'Ungültiges Datumsformat',
|
|
|
|
|
}),
|
2025-12-05 22:18:25 +01:00
|
|
|
notes: z.string().optional().transform((val) => val?.trim() || undefined),
|
2025-12-05 22:17:50 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const wearPartSchema = z.object({
|
|
|
|
|
bikeId: z.string().min(1, 'Fahrrad-ID ist erforderlich'),
|
|
|
|
|
type: WearPartType,
|
|
|
|
|
brand: z.string().optional(),
|
|
|
|
|
model: z.string().optional(),
|
|
|
|
|
installDate: z.string().datetime().or(z.date()),
|
|
|
|
|
installMileage: z.number().int().min(0).default(0),
|
|
|
|
|
serviceInterval: z.number().int().min(1, 'Service-Intervall muss mindestens 1 km sein'),
|
|
|
|
|
status: WearPartStatus.default('ACTIVE'),
|
|
|
|
|
cost: z.number().positive().optional(),
|
|
|
|
|
notes: z.string().optional(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const maintenanceHistorySchema = z.object({
|
|
|
|
|
wearPartId: z.string().min(1, 'Verschleißteil-ID ist erforderlich'),
|
|
|
|
|
date: z.string().datetime().or(z.date()),
|
|
|
|
|
mileage: z.number().int().min(0),
|
|
|
|
|
action: MaintenanceAction,
|
|
|
|
|
notes: z.string().optional(),
|
|
|
|
|
cost: z.number().positive().optional(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export type BikeInput = z.infer<typeof bikeSchema>
|
|
|
|
|
export type WearPartInput = z.infer<typeof wearPartSchema>
|
|
|
|
|
export type MaintenanceHistoryInput = z.infer<typeof maintenanceHistorySchema>
|
|
|
|
|
|