Fix: TypeScript-Fehler für Multi-Treatment-Migration beheben

- admin-calendar.tsx: getTreatmentNames für treatments[] angepasst
- admin-calendar.tsx: getAvailableTimes für treatmentIds[] umgestellt
- admin-calendar.tsx: createManualBooking sendet treatments[] statt treatmentId
- cancellation.ts: treatmentId optional behandeln (Rückwärtskompatibilität)
- review-submission-page.tsx: treatmentName durch treatments[] ersetzt
- booking-status-page.tsx: proposed date/time als optional markiert

Docker-Build erfolgreich getestet.
This commit is contained in:
2025-10-08 19:57:10 +02:00
parent ebd9d8a72e
commit 63384aa209
4 changed files with 150 additions and 26 deletions

View File

@@ -47,7 +47,7 @@ export function AdminCalendar() {
...queryClient.recurringAvailability.getAvailableTimes.queryOptions({ ...queryClient.recurringAvailability.getAvailableTimes.queryOptions({
input: { input: {
date: createFormData.appointmentDate, date: createFormData.appointmentDate,
treatmentId: createFormData.treatmentId treatmentIds: createFormData.treatmentId ? [createFormData.treatmentId] : []
} }
}), }),
enabled: !!createFormData.appointmentDate && !!createFormData.treatmentId enabled: !!createFormData.appointmentDate && !!createFormData.treatmentId
@@ -58,7 +58,16 @@ export function AdminCalendar() {
...queryClient.recurringAvailability.getAvailableTimes.queryOptions({ ...queryClient.recurringAvailability.getAvailableTimes.queryOptions({
input: { input: {
date: rescheduleFormData.appointmentDate, date: rescheduleFormData.appointmentDate,
treatmentId: (showRescheduleModal ? bookings?.find(b => b.id === showRescheduleModal)?.treatmentId : '') || '' treatmentIds: (() => {
const booking = showRescheduleModal ? bookings?.find(b => b.id === showRescheduleModal) : null;
if (!booking) return [];
// Use new treatments array if available
if (booking.treatments && Array.isArray(booking.treatments) && booking.treatments.length > 0) {
return booking.treatments.map((t: any) => t.id);
}
// Fallback to deprecated treatmentId for backward compatibility
return booking.treatmentId ? [booking.treatmentId] : [];
})()
} }
}), }),
enabled: !!showRescheduleModal && !!rescheduleFormData.appointmentDate enabled: !!showRescheduleModal && !!rescheduleFormData.appointmentDate
@@ -86,8 +95,16 @@ export function AdminCalendar() {
queryClient.bookings.generateCalDAVToken.mutationOptions() queryClient.bookings.generateCalDAVToken.mutationOptions()
); );
const getTreatmentName = (treatmentId: string) => { const getTreatmentNames = (booking: any) => {
return treatments?.find(t => t.id === treatmentId)?.name || "Unbekannte Behandlung"; // Handle new treatments array structure
if (booking.treatments && Array.isArray(booking.treatments) && booking.treatments.length > 0) {
return booking.treatments.map((t: any) => t.name).join(", ");
}
// Fallback to deprecated treatmentId for backward compatibility
if (booking.treatmentId) {
return treatments?.find(t => t.id === booking.treatmentId)?.name || "Unbekannte Behandlung";
}
return "Keine Behandlung";
}; };
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
@@ -219,9 +236,29 @@ export function AdminCalendar() {
const sessionId = localStorage.getItem('sessionId'); const sessionId = localStorage.getItem('sessionId');
if (!sessionId) return; if (!sessionId) return;
// Convert treatmentId to treatments array
const selectedTreatment = treatments?.find(t => t.id === createFormData.treatmentId);
if (!selectedTreatment) {
setCreateError('Bitte wähle eine Behandlung aus.');
return;
}
const treatmentsArray = [{
id: selectedTreatment.id,
name: selectedTreatment.name,
duration: selectedTreatment.duration,
price: selectedTreatment.price
}];
createManualBooking({ createManualBooking({
sessionId, sessionId,
...createFormData treatments: treatmentsArray,
customerName: createFormData.customerName,
appointmentDate: createFormData.appointmentDate,
appointmentTime: createFormData.appointmentTime,
customerEmail: createFormData.customerEmail,
customerPhone: createFormData.customerPhone,
notes: createFormData.notes
}, { }, {
onSuccess: () => { onSuccess: () => {
setShowCreateModal(false); setShowCreateModal(false);
@@ -469,7 +506,7 @@ export function AdminCalendar() {
<div <div
key={booking.id} key={booking.id}
className={`text-xs p-1 rounded border-l-2 ${getStatusColor(booking.status)} truncate`} className={`text-xs p-1 rounded border-l-2 ${getStatusColor(booking.status)} truncate`}
title={`${booking.customerName} - ${getTreatmentName(booking.treatmentId)} (${booking.appointmentTime})`} title={`${booking.customerName} - ${getTreatmentNames(booking)} (${booking.appointmentTime})`}
> >
<div className="font-medium">{booking.appointmentTime}</div> <div className="font-medium">{booking.appointmentTime}</div>
<div className="truncate">{booking.customerName}</div> <div className="truncate">{booking.customerName}</div>
@@ -526,7 +563,7 @@ export function AdminCalendar() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600">
<div> <div>
<strong>Behandlung:</strong> {getTreatmentName(booking.treatmentId)} <strong>Behandlung:</strong> {getTreatmentNames(booking)}
</div> </div>
<div> <div>
<strong>Uhrzeit:</strong> {booking.appointmentTime} <strong>Uhrzeit:</strong> {booking.appointmentTime}
@@ -842,7 +879,7 @@ export function AdminCalendar() {
{(() => { {(() => {
const booking = bookings?.find(b => b.id === showRescheduleModal); const booking = bookings?.find(b => b.id === showRescheduleModal);
const treatmentName = booking ? getTreatmentName(booking.treatmentId) : ''; const treatmentName = booking ? getTreatmentNames(booking) : '';
return booking ? ( return booking ? (
<div className="mb-4 text-sm text-gray-700"> <div className="mb-4 text-sm text-gray-700">
<div className="mb-2"><strong>Kunde:</strong> {booking.customerName}</div> <div className="mb-2"><strong>Kunde:</strong> {booking.customerName}</div>

View File

@@ -45,7 +45,7 @@ interface RescheduleProposalDetails {
totalPrice: number; totalPrice: number;
}; };
original: { date: string; time: string }; original: { date: string; time: string };
proposed: { date: string; time: string }; proposed: { date?: string; time?: string };
expiresAt: string; expiresAt: string;
hoursUntilExpiry: number; hoursUntilExpiry: number;
isExpired: boolean; isExpired: boolean;
@@ -203,7 +203,7 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
if (oneClickAction === 'accept') { if (oneClickAction === 'accept') {
const confirmAccept = window.confirm( const confirmAccept = window.confirm(
`Möchtest du den neuen Termin am ${rescheduleProposal.proposed.date} um ${rescheduleProposal.proposed.time} Uhr akzeptieren?` `Möchtest du den neuen Termin am ${rescheduleProposal.proposed.date || 'TBD'} um ${rescheduleProposal.proposed.time || 'TBD'} Uhr akzeptieren?`
); );
if (confirmAccept) { if (confirmAccept) {
acceptMutation.mutate({ token }); acceptMutation.mutate({ token });
@@ -381,7 +381,7 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
</div> </div>
<div className="border rounded-lg p-4 bg-orange-50"> <div className="border rounded-lg p-4 bg-orange-50">
<div className="text-sm text-orange-700 font-semibold mb-1">Neuer Vorschlag</div> <div className="text-sm text-orange-700 font-semibold mb-1">Neuer Vorschlag</div>
<div className="text-gray-900 font-medium">{rescheduleProposal.proposed.date} um {rescheduleProposal.proposed.time} Uhr</div> <div className="text-gray-900 font-medium">{rescheduleProposal.proposed.date || 'TBD'} um {rescheduleProposal.proposed.time || 'TBD'} Uhr</div>
<div className="text-gray-700 text-sm mt-2"> <div className="text-gray-700 text-sm mt-2">
{rescheduleProposal.booking.treatments && rescheduleProposal.booking.treatments.length > 0 ? ( {rescheduleProposal.booking.treatments && rescheduleProposal.booking.treatments.length > 0 ? (
<> <>

View File

@@ -139,7 +139,11 @@ export default function ReviewSubmissionPage({ token }: ReviewSubmissionPageProp
</div> </div>
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Behandlung:</span> <span className="text-gray-600">Behandlung:</span>
<span className="font-medium text-gray-900">{booking.treatmentName}</span> <span className="font-medium text-gray-900">
{booking.treatments && booking.treatments.length > 0
? booking.treatments.map((t: any) => t.name).join(", ")
: "Keine Behandlung"}
</span>
</div> </div>
<div className="flex justify-between py-2"> <div className="flex justify-between py-2">
<span className="text-gray-600">Name:</span> <span className="text-gray-600">Name:</span>

View File

@@ -28,7 +28,15 @@ const cancellationKV = createKV<BookingAccessToken>("cancellation_tokens");
// Types for booking and availability // Types for booking and availability
type Booking = { type Booking = {
id: string; id: string;
treatmentId: string; treatments: Array<{
id: string;
name: string;
duration: number;
price: number;
}>;
// Deprecated fields for backward compatibility
treatmentId?: string;
bookedDurationMinutes?: number;
customerName: string; customerName: string;
customerEmail?: string; customerEmail?: string;
customerPhone?: string; customerPhone?: string;
@@ -120,10 +128,43 @@ const getBookingByToken = os
throw new Error("Booking not found"); throw new Error("Booking not found");
} }
// Get treatment details // Handle treatments array
let treatments: Array<{id: string; name: string; duration: number; price: number}>;
let totalDuration: number;
let totalPrice: number;
if (booking.treatments && booking.treatments.length > 0) {
// New bookings with treatments array
treatments = booking.treatments;
totalDuration = treatments.reduce((sum, t) => sum + t.duration, 0);
totalPrice = treatments.reduce((sum, t) => sum + t.price, 0);
} else if (booking.treatmentId) {
// Old bookings with single treatmentId (backward compatibility)
const treatmentsKV = createKV<any>("treatments"); const treatmentsKV = createKV<any>("treatments");
const treatment = await treatmentsKV.getItem(booking.treatmentId); const treatment = await treatmentsKV.getItem(booking.treatmentId);
if (treatment) {
treatments = [{
id: treatment.id,
name: treatment.name,
duration: treatment.duration,
price: treatment.price,
}];
totalDuration = treatment.duration;
totalPrice = treatment.price;
} else {
// Fallback if treatment not found
treatments = [];
totalDuration = booking.bookedDurationMinutes || 60;
totalPrice = 0;
}
} else {
// Edge case: no treatments and no treatmentId
treatments = [];
totalDuration = 0;
totalPrice = 0;
}
// Calculate if cancellation is still possible // Calculate if cancellation is still possible
const minStornoTimespan = parseInt(process.env.MIN_STORNO_TIMESPAN || "24"); const minStornoTimespan = parseInt(process.env.MIN_STORNO_TIMESPAN || "24");
const appointmentDateTime = new Date(`${booking.appointmentDate}T${booking.appointmentTime}:00`); const appointmentDateTime = new Date(`${booking.appointmentDate}T${booking.appointmentTime}:00`);
@@ -140,10 +181,9 @@ const getBookingByToken = os
customerPhone: booking.customerPhone, customerPhone: booking.customerPhone,
appointmentDate: booking.appointmentDate, appointmentDate: booking.appointmentDate,
appointmentTime: booking.appointmentTime, appointmentTime: booking.appointmentTime,
treatmentId: booking.treatmentId, treatments,
treatmentName: treatment?.name || "Unbekannte Behandlung", totalDuration,
treatmentDuration: treatment?.duration || 60, totalPrice,
treatmentPrice: treatment?.price || 0,
status: booking.status, status: booking.status,
notes: booking.notes, notes: booking.notes,
formattedDate: formatDateGerman(booking.appointmentDate), formattedDate: formatDateGerman(booking.appointmentDate),
@@ -284,9 +324,43 @@ export const router = {
throw new Error("Booking not found"); throw new Error("Booking not found");
} }
// Handle treatments array
let treatments: Array<{id: string; name: string; duration: number; price: number}>;
let totalDuration: number;
let totalPrice: number;
if (booking.treatments && booking.treatments.length > 0) {
// New bookings with treatments array
treatments = booking.treatments;
totalDuration = treatments.reduce((sum, t) => sum + t.duration, 0);
totalPrice = treatments.reduce((sum, t) => sum + t.price, 0);
} else if (booking.treatmentId) {
// Old bookings with single treatmentId (backward compatibility)
const treatmentsKV = createKV<any>("treatments"); const treatmentsKV = createKV<any>("treatments");
const treatment = await treatmentsKV.getItem(booking.treatmentId); const treatment = await treatmentsKV.getItem(booking.treatmentId);
if (treatment) {
treatments = [{
id: treatment.id,
name: treatment.name,
duration: treatment.duration,
price: treatment.price,
}];
totalDuration = treatment.duration;
totalPrice = treatment.price;
} else {
// Fallback if treatment not found
treatments = [];
totalDuration = booking.bookedDurationMinutes || 60;
totalPrice = 0;
}
} else {
// Edge case: no treatments and no treatmentId
treatments = [];
totalDuration = 0;
totalPrice = 0;
}
const now = new Date(); const now = new Date();
const isExpired = new Date(proposal.expiresAt) <= now; const isExpired = new Date(proposal.expiresAt) <= now;
const hoursUntilExpiry = isExpired ? 0 : Math.max(0, Math.round((new Date(proposal.expiresAt).getTime() - now.getTime()) / (1000 * 60 * 60))); const hoursUntilExpiry = isExpired ? 0 : Math.max(0, Math.round((new Date(proposal.expiresAt).getTime() - now.getTime()) / (1000 * 60 * 60)));
@@ -298,8 +372,9 @@ export const router = {
customerEmail: booking.customerEmail, customerEmail: booking.customerEmail,
customerPhone: booking.customerPhone, customerPhone: booking.customerPhone,
status: booking.status, status: booking.status,
treatmentId: booking.treatmentId, treatments,
treatmentName: treatment?.name || "Unbekannte Behandlung", totalDuration,
totalPrice,
}, },
original: { original: {
date: proposal.originalDate || booking.appointmentDate, date: proposal.originalDate || booking.appointmentDate,
@@ -358,14 +433,22 @@ export const router = {
const booking = await bookingsKV.getItem(proposal.bookingId); const booking = await bookingsKV.getItem(proposal.bookingId);
if (booking) { if (booking) {
const treatmentsKV = createKV<any>("treatments"); const treatmentsKV = createKV<any>("treatments");
// Get treatment name(s) from new treatments array or fallback to deprecated treatmentId
let treatmentName = "Unbekannte Behandlung";
if (booking.treatments && Array.isArray(booking.treatments) && booking.treatments.length > 0) {
treatmentName = booking.treatments.map((t: any) => t.name).join(", ");
} else if (booking.treatmentId) {
const treatment = await treatmentsKV.getItem(booking.treatmentId); const treatment = await treatmentsKV.getItem(booking.treatmentId);
treatmentName = treatment?.name || "Unbekannte Behandlung";
}
expiredDetails.push({ expiredDetails.push({
customerName: booking.customerName, customerName: booking.customerName,
originalDate: proposal.originalDate || booking.appointmentDate, originalDate: proposal.originalDate || booking.appointmentDate,
originalTime: proposal.originalTime || booking.appointmentTime, originalTime: proposal.originalTime || booking.appointmentTime,
proposedDate: proposal.proposedDate!, proposedDate: proposal.proposedDate!,
proposedTime: proposal.proposedTime!, proposedTime: proposal.proposedTime!,
treatmentName: treatment?.name || "Unbekannte Behandlung", treatmentName: treatmentName,
customerEmail: booking.customerEmail, customerEmail: booking.customerEmail,
customerPhone: booking.customerPhone, customerPhone: booking.customerPhone,
expiredAt: proposal.expiresAt, expiredAt: proposal.expiresAt,