Refactor appointment detail and booking components to implement time slot sorting by predefined order. Enhance the display of selected slots by grouping and sorting them by date and time, improving clarity and user experience across admin and user interfaces.
This commit is contained in:
parent
9f6bb98edb
commit
97b04e7593
@ -413,6 +413,13 @@ export default function AppointmentDetailPage() {
|
|||||||
evening: "Evening",
|
evening: "Evening",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Time slot order: morning, afternoon (lunchtime), evening
|
||||||
|
const timeSlotOrder: Record<string, number> = {
|
||||||
|
morning: 0,
|
||||||
|
afternoon: 1,
|
||||||
|
evening: 2,
|
||||||
|
};
|
||||||
|
|
||||||
// Group slots by date
|
// Group slots by date
|
||||||
const slotsByDate: Record<string, typeof appointment.selected_slots> = {};
|
const slotsByDate: Record<string, typeof appointment.selected_slots> = {};
|
||||||
appointment.selected_slots.forEach((slot: any) => {
|
appointment.selected_slots.forEach((slot: any) => {
|
||||||
@ -423,36 +430,52 @@ export default function AppointmentDetailPage() {
|
|||||||
slotsByDate[date].push(slot);
|
slotsByDate[date].push(slot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sort dates and slots within each date
|
||||||
|
const sortedDates = Object.keys(slotsByDate).sort((a, b) => {
|
||||||
|
return new Date(a).getTime() - new Date(b).getTime();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{Object.entries(slotsByDate).map(([date, slots]) => (
|
{sortedDates.map((date) => {
|
||||||
<div key={date} className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
// Sort slots within this date by time slot order
|
||||||
<div className="mb-3">
|
const slots = slotsByDate[date].sort((a: any, b: any) => {
|
||||||
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
const aSlot = String(a.time_slot).toLowerCase().trim();
|
||||||
{formatShortDate(date)}
|
const bSlot = String(b.time_slot).toLowerCase().trim();
|
||||||
</p>
|
const aOrder = timeSlotOrder[aSlot] ?? 999;
|
||||||
{slots.length > 0 && slots[0]?.day !== undefined && (
|
const bOrder = timeSlotOrder[bSlot] ?? 999;
|
||||||
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
return aOrder - bOrder;
|
||||||
{dayNames[slots[0].day] || `Day ${slots[0].day}`}
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={date} className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||||
|
{formatShortDate(date)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
{slots.length > 0 && slots[0]?.day !== undefined && (
|
||||||
|
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||||
|
{dayNames[slots[0].day] || `Day ${slots[0].day}`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{slots.map((slot: any, idx: number) => {
|
||||||
|
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
||||||
|
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200"}`}
|
||||||
|
>
|
||||||
|
{timeLabel}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
);
|
||||||
{slots.map((slot: any, idx: number) => {
|
})}
|
||||||
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
|
||||||
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
key={idx}
|
|
||||||
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200"}`}
|
|
||||||
>
|
|
||||||
{timeLabel}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
@ -154,12 +154,23 @@ export default function BookNowPage() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Convert Map values to array and sort by day number
|
// Time slot order: morning, afternoon (lunchtime), evening
|
||||||
|
const timeSlotOrder: Record<string, number> = {
|
||||||
|
morning: 0,
|
||||||
|
afternoon: 1,
|
||||||
|
evening: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert Map values to array, sort slots, and sort by day number
|
||||||
return Array.from(dayMap.values())
|
return Array.from(dayMap.values())
|
||||||
.map(day => ({
|
.map(day => ({
|
||||||
day: day.day,
|
day: day.day,
|
||||||
dayName: day.dayName,
|
dayName: day.dayName,
|
||||||
availableSlots: Array.from(day.availableSlots),
|
availableSlots: Array.from(day.availableSlots).sort((a, b) => {
|
||||||
|
const aOrder = timeSlotOrder[a.toLowerCase().trim()] ?? 999;
|
||||||
|
const bOrder = timeSlotOrder[b.toLowerCase().trim()] ?? 999;
|
||||||
|
return aOrder - bOrder;
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.day - b.day);
|
.sort((a, b) => a.day - b.day);
|
||||||
}
|
}
|
||||||
@ -172,6 +183,13 @@ export default function BookNowPage() {
|
|||||||
: (weeklyAvailability as any)?.week;
|
: (weeklyAvailability as any)?.week;
|
||||||
|
|
||||||
if (weekArray && Array.isArray(weekArray)) {
|
if (weekArray && Array.isArray(weekArray)) {
|
||||||
|
// Time slot order: morning, afternoon (lunchtime), evening
|
||||||
|
const timeSlotOrder: Record<string, number> = {
|
||||||
|
morning: 0,
|
||||||
|
afternoon: 1,
|
||||||
|
evening: 2,
|
||||||
|
};
|
||||||
|
|
||||||
return weekArray
|
return weekArray
|
||||||
.filter(day => {
|
.filter(day => {
|
||||||
const dayNum = Number(day.day);
|
const dayNum = Number(day.day);
|
||||||
@ -186,7 +204,11 @@ export default function BookNowPage() {
|
|||||||
.map(day => ({
|
.map(day => ({
|
||||||
day: Number(day.day),
|
day: Number(day.day),
|
||||||
dayName: day.day_name || 'Unknown',
|
dayName: day.day_name || 'Unknown',
|
||||||
availableSlots: day.available_slots || [],
|
availableSlots: (day.available_slots || []).sort((a: string, b: string) => {
|
||||||
|
const aOrder = timeSlotOrder[String(a).toLowerCase().trim()] ?? 999;
|
||||||
|
const bOrder = timeSlotOrder[String(b).toLowerCase().trim()] ?? 999;
|
||||||
|
return aOrder - bOrder;
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.day - b.day);
|
.sort((a, b) => a.day - b.day);
|
||||||
}
|
}
|
||||||
@ -752,34 +774,6 @@ export default function BookNowPage() {
|
|||||||
<p className={`text-xs ${isDark ? 'text-gray-400' : 'text-gray-500'} mb-3`}>
|
<p className={`text-xs ${isDark ? 'text-gray-400' : 'text-gray-500'} mb-3`}>
|
||||||
Select one or more day-time combinations that work for you. You can select multiple time slots for the same day (e.g., Monday Morning and Monday Evening).
|
Select one or more day-time combinations that work for you. You can select multiple time slots for the same day (e.g., Monday Morning and Monday Evening).
|
||||||
</p>
|
</p>
|
||||||
{/* Selected Slots Summary */}
|
|
||||||
{formData.selectedSlots && formData.selectedSlots.length > 0 && (
|
|
||||||
<div className={`mb-4 p-3 rounded-lg border ${isDark ? 'bg-gray-800/50 border-gray-700' : 'bg-rose-50/50 border-rose-200'}`}>
|
|
||||||
<p className={`text-xs font-medium mb-2 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
|
||||||
Selected: {formData.selectedSlots.length} time slot{formData.selectedSlots.length !== 1 ? 's' : ''}
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-wrap gap-1.5">
|
|
||||||
{formData.selectedSlots.map((slot, idx) => {
|
|
||||||
const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
||||||
const timeSlotLabels: Record<string, string> = {
|
|
||||||
morning: "Morning",
|
|
||||||
afternoon: "Lunchtime",
|
|
||||||
evening: "Evening",
|
|
||||||
};
|
|
||||||
const dayName = dayNames[slot.day] || `Day ${slot.day}`;
|
|
||||||
const timeLabel = timeSlotLabels[String(slot.time_slot).toLowerCase()] || slot.time_slot;
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
key={idx}
|
|
||||||
className={`px-2 py-1 rounded text-xs font-medium ${isDark ? 'bg-rose-600/30 text-rose-300 border border-rose-500/30' : 'bg-rose-100 text-rose-700 border border-rose-200'}`}
|
|
||||||
>
|
|
||||||
{dayName} {timeLabel}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{availableDaysOfWeek.map((dayInfo, dayIndex) => {
|
{availableDaysOfWeek.map((dayInfo, dayIndex) => {
|
||||||
// Ensure day is always a valid number (already validated in useMemo)
|
// Ensure day is always a valid number (already validated in useMemo)
|
||||||
|
|||||||
@ -352,6 +352,13 @@ export default function UserAppointmentDetailPage() {
|
|||||||
evening: "Evening",
|
evening: "Evening",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Time slot order: morning, afternoon (lunchtime), evening
|
||||||
|
const timeSlotOrder: Record<string, number> = {
|
||||||
|
morning: 0,
|
||||||
|
afternoon: 1,
|
||||||
|
evening: 2,
|
||||||
|
};
|
||||||
|
|
||||||
// Group slots by date
|
// Group slots by date
|
||||||
const slotsByDate: Record<string, typeof appointment.selected_slots> = {};
|
const slotsByDate: Record<string, typeof appointment.selected_slots> = {};
|
||||||
appointment.selected_slots.forEach((slot: any) => {
|
appointment.selected_slots.forEach((slot: any) => {
|
||||||
@ -362,36 +369,52 @@ export default function UserAppointmentDetailPage() {
|
|||||||
slotsByDate[date].push(slot);
|
slotsByDate[date].push(slot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sort dates and slots within each date
|
||||||
|
const sortedDates = Object.keys(slotsByDate).sort((a, b) => {
|
||||||
|
return new Date(a).getTime() - new Date(b).getTime();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{Object.entries(slotsByDate).map(([date, slots]) => (
|
{sortedDates.map((date) => {
|
||||||
<div key={date} className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
// Sort slots within this date by time slot order
|
||||||
<div className="mb-3">
|
const slots = slotsByDate[date].sort((a: any, b: any) => {
|
||||||
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
const aSlot = String(a.time_slot).toLowerCase().trim();
|
||||||
{formatShortDate(date)}
|
const bSlot = String(b.time_slot).toLowerCase().trim();
|
||||||
</p>
|
const aOrder = timeSlotOrder[aSlot] ?? 999;
|
||||||
{slots.length > 0 && slots[0]?.day !== undefined && (
|
const bOrder = timeSlotOrder[bSlot] ?? 999;
|
||||||
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
return aOrder - bOrder;
|
||||||
{dayNames[slots[0].day] || `Day ${slots[0].day}`}
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={date} className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||||
|
{formatShortDate(date)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
{slots.length > 0 && slots[0]?.day !== undefined && (
|
||||||
|
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||||
|
{dayNames[slots[0].day] || `Day ${slots[0].day}`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{slots.map((slot: any, idx: number) => {
|
||||||
|
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
||||||
|
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200"}`}
|
||||||
|
>
|
||||||
|
{timeLabel}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
);
|
||||||
{slots.map((slot: any, idx: number) => {
|
})}
|
||||||
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
|
||||||
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
key={idx}
|
|
||||||
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200"}`}
|
|
||||||
>
|
|
||||||
{timeLabel}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user