Fix: Verschleißteil-Formular Datumsvalidierung und Schriftfarbe
- installDate Schema akzeptiert jetzt YYYY-MM-DD Format (HTML date input) - maintenanceHistorySchema date akzeptiert jetzt YYYY-MM-DD Format - Transform konvertiert Datumsstrings automatisch zu Date-Objekten - API-Routes verwenden validierte Date-Objekte direkt - Alle Formularfelder haben jetzt schwarze Schriftfarbe (text-black) - Optional-Felder werden getrimmt (brand, model, notes)
This commit is contained in:
@@ -48,7 +48,7 @@ export async function POST(
|
|||||||
type: validatedData.type,
|
type: validatedData.type,
|
||||||
brand: validatedData.brand,
|
brand: validatedData.brand,
|
||||||
model: validatedData.model,
|
model: validatedData.model,
|
||||||
installDate: new Date(validatedData.installDate),
|
installDate: validatedData.installDate,
|
||||||
installMileage: validatedData.installMileage,
|
installMileage: validatedData.installMileage,
|
||||||
serviceInterval: validatedData.serviceInterval,
|
serviceInterval: validatedData.serviceInterval,
|
||||||
status: validatedData.status,
|
status: validatedData.status,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export async function POST(
|
|||||||
const history = await prisma.maintenanceHistory.create({
|
const history = await prisma.maintenanceHistory.create({
|
||||||
data: {
|
data: {
|
||||||
wearPartId: params.id,
|
wearPartId: params.id,
|
||||||
date: new Date(validatedData.date),
|
date: validatedData.date,
|
||||||
mileage: validatedData.mileage,
|
mileage: validatedData.mileage,
|
||||||
action: validatedData.action,
|
action: validatedData.action,
|
||||||
notes: validatedData.notes,
|
notes: validatedData.notes,
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default function BikeForm({ onSuccess, onCancel }: BikeFormProps) {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, name: e.target.value })
|
setFormData({ ...formData, name: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.name && (
|
{errors.name && (
|
||||||
@@ -112,7 +112,7 @@ export default function BikeForm({ onSuccess, onCancel }: BikeFormProps) {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, brand: e.target.value })
|
setFormData({ ...formData, brand: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ export default function BikeForm({ onSuccess, onCancel }: BikeFormProps) {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, model: e.target.value })
|
setFormData({ ...formData, model: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +141,7 @@ export default function BikeForm({ onSuccess, onCancel }: BikeFormProps) {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, purchaseDate: e.target.value })
|
setFormData({ ...formData, purchaseDate: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export default function BikeForm({ onSuccess, onCancel }: BikeFormProps) {
|
|||||||
setFormData({ ...formData, notes: e.target.value })
|
setFormData({ ...formData, notes: e.target.value })
|
||||||
}
|
}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default function MaintenanceForm({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, date: e.target.value })
|
setFormData({ ...formData, date: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.date && (
|
{errors.date && (
|
||||||
@@ -124,7 +124,7 @@ export default function MaintenanceForm({
|
|||||||
setFormData({ ...formData, mileage: Number(e.target.value) })
|
setFormData({ ...formData, mileage: Number(e.target.value) })
|
||||||
}
|
}
|
||||||
min="0"
|
min="0"
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.mileage && (
|
{errors.mileage && (
|
||||||
@@ -143,7 +143,7 @@ export default function MaintenanceForm({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, action: e.target.value })
|
setFormData({ ...formData, action: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
{maintenanceActions.map((action) => (
|
{maintenanceActions.map((action) => (
|
||||||
@@ -169,7 +169,7 @@ export default function MaintenanceForm({
|
|||||||
setFormData({ ...formData, cost: e.target.value })
|
setFormData({ ...formData, cost: e.target.value })
|
||||||
}
|
}
|
||||||
min="0"
|
min="0"
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,7 +184,7 @@ export default function MaintenanceForm({
|
|||||||
setFormData({ ...formData, notes: e.target.value })
|
setFormData({ ...formData, notes: e.target.value })
|
||||||
}
|
}
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export default function WearPartForm({
|
|||||||
<select
|
<select
|
||||||
value={formData.type}
|
value={formData.type}
|
||||||
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
{wearPartTypes.map((type) => (
|
{wearPartTypes.map((type) => (
|
||||||
@@ -160,7 +160,7 @@ export default function WearPartForm({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, brand: e.target.value })
|
setFormData({ ...formData, brand: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ export default function WearPartForm({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, model: e.target.value })
|
setFormData({ ...formData, model: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +190,7 @@ export default function WearPartForm({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, installDate: e.target.value })
|
setFormData({ ...formData, installDate: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.installDate && (
|
{errors.installDate && (
|
||||||
@@ -212,7 +212,7 @@ export default function WearPartForm({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
min="0"
|
min="0"
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.installMileage && (
|
{errors.installMileage && (
|
||||||
@@ -238,7 +238,7 @@ export default function WearPartForm({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
min="1"
|
min="1"
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{errors.serviceInterval && (
|
{errors.serviceInterval && (
|
||||||
@@ -257,7 +257,7 @@ export default function WearPartForm({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, status: e.target.value })
|
setFormData({ ...formData, status: e.target.value })
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
>
|
>
|
||||||
{wearPartStatuses.map((status) => (
|
{wearPartStatuses.map((status) => (
|
||||||
<option key={status} value={status}>
|
<option key={status} value={status}>
|
||||||
@@ -280,7 +280,7 @@ export default function WearPartForm({
|
|||||||
setFormData({ ...formData, cost: e.target.value })
|
setFormData({ ...formData, cost: e.target.value })
|
||||||
}
|
}
|
||||||
min="0"
|
min="0"
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ export default function WearPartForm({
|
|||||||
setFormData({ ...formData, notes: e.target.value })
|
setFormData({ ...formData, notes: e.target.value })
|
||||||
}
|
}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -72,22 +72,60 @@ export const bikeSchema = z.object({
|
|||||||
export const wearPartSchema = z.object({
|
export const wearPartSchema = z.object({
|
||||||
bikeId: z.string().min(1, 'Fahrrad-ID ist erforderlich'),
|
bikeId: z.string().min(1, 'Fahrrad-ID ist erforderlich'),
|
||||||
type: WearPartType,
|
type: WearPartType,
|
||||||
brand: z.string().optional(),
|
brand: z.string().optional().transform((val) => val?.trim() || undefined),
|
||||||
model: z.string().optional(),
|
model: z.string().optional().transform((val) => val?.trim() || undefined),
|
||||||
installDate: z.string().datetime().or(z.date()),
|
installDate: z
|
||||||
|
.union([z.date(), z.string()])
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return new Date() // Default to today if not provided
|
||||||
|
if (val instanceof Date) {
|
||||||
|
return isNaN(val.getTime()) ? new Date() : 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()) ? new Date() : date
|
||||||
|
}
|
||||||
|
// Handle ISO datetime format or any other parseable date string
|
||||||
|
const date = new Date(val)
|
||||||
|
return isNaN(date.getTime()) ? new Date() : date
|
||||||
|
}
|
||||||
|
return new Date()
|
||||||
|
})
|
||||||
|
.pipe(z.date()),
|
||||||
installMileage: z.number().int().min(0).default(0),
|
installMileage: z.number().int().min(0).default(0),
|
||||||
serviceInterval: z.number().int().min(1, 'Service-Intervall muss mindestens 1 km sein'),
|
serviceInterval: z.number().int().min(1, 'Service-Intervall muss mindestens 1 km sein'),
|
||||||
status: WearPartStatus.default('ACTIVE'),
|
status: WearPartStatus.default('ACTIVE'),
|
||||||
cost: z.number().positive().optional(),
|
cost: z.number().positive().optional(),
|
||||||
notes: z.string().optional(),
|
notes: z.string().optional().transform((val) => val?.trim() || undefined),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const maintenanceHistorySchema = z.object({
|
export const maintenanceHistorySchema = z.object({
|
||||||
wearPartId: z.string().min(1, 'Verschleißteil-ID ist erforderlich'),
|
wearPartId: z.string().min(1, 'Verschleißteil-ID ist erforderlich'),
|
||||||
date: z.string().datetime().or(z.date()),
|
date: z
|
||||||
|
.union([z.date(), z.string()])
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return new Date() // Default to today if not provided
|
||||||
|
if (val instanceof Date) {
|
||||||
|
return isNaN(val.getTime()) ? new Date() : 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()) ? new Date() : date
|
||||||
|
}
|
||||||
|
// Handle ISO datetime format or any other parseable date string
|
||||||
|
const date = new Date(val)
|
||||||
|
return isNaN(date.getTime()) ? new Date() : date
|
||||||
|
}
|
||||||
|
return new Date()
|
||||||
|
})
|
||||||
|
.pipe(z.date()),
|
||||||
mileage: z.number().int().min(0),
|
mileage: z.number().int().min(0),
|
||||||
action: MaintenanceAction,
|
action: MaintenanceAction,
|
||||||
notes: z.string().optional(),
|
notes: z.string().optional().transform((val) => val?.trim() || undefined),
|
||||||
cost: z.number().positive().optional(),
|
cost: z.number().positive().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user