Refactor appointment detail rendering to replace "Matching Slots" with "Selected Slots" for improved clarity. Update UI elements to reflect changes in color scheme and enhance the display of availability preferences. Remove legacy matching slots logic to streamline the component and focus on selected time slots.

This commit is contained in:
iamkiddy 2025-12-03 16:25:28 +00:00
parent 01dcf9abe5
commit 2d1409682f
2 changed files with 30 additions and 254 deletions

View File

@ -367,13 +367,18 @@ export default function AppointmentDetailPage() {
</div> </div>
)} )}
{/* Selected Slots */} {/* Selected Slots (replacing Matching Slots) */}
{appointment.selected_slots && Array.isArray(appointment.selected_slots) && appointment.selected_slots.length > 0 && ( {appointment.selected_slots && Array.isArray(appointment.selected_slots) && appointment.selected_slots.length > 0 && (
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}> <div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
<div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}> <div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}>
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}> <h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}>
<CalendarCheck className={`w-5 h-5 ${isDark ? "text-blue-400" : "text-blue-600"}`} /> <CalendarCheck className={`w-5 h-5 ${isDark ? "text-green-400" : "text-green-600"}`} />
Selected Time Slots Selected Time Slots
{appointment.are_preferences_available !== undefined && (
<span className={`ml-auto px-3 py-1 text-xs font-medium rounded-full ${appointment.are_preferences_available ? (isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200") : (isDark ? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30" : "bg-yellow-50 text-yellow-700 border border-yellow-200")}`}>
{appointment.are_preferences_available ? "All Available" : "Partially Available"}
</span>
)}
</h2> </h2>
</div> </div>
<div className="p-6"> <div className="p-6">
@ -400,14 +405,19 @@ export default function AppointmentDetailPage() {
return ( return (
<div <div
key={idx} key={idx}
className={`px-4 py-3 rounded-xl border ${isDark ? "bg-blue-500/10 border-blue-500/30" : "bg-blue-50 border-blue-200"}`} className={`px-4 py-3 rounded-xl border ${isDark ? "bg-green-500/10 border-green-500/30" : "bg-green-50 border-green-200"}`}
> >
<p className={`text-sm font-semibold ${isDark ? "text-blue-300" : "text-blue-700"}`}> <p className={`text-sm font-semibold ${isDark ? "text-green-300" : "text-green-700"}`}>
{dayName} {dayName}
</p> </p>
<p className={`text-xs mt-1 ${isDark ? "text-blue-400" : "text-blue-600"}`}> <p className={`text-xs mt-1 ${isDark ? "text-green-400" : "text-green-600"}`}>
{timeLabel} {timeLabel}
</p> </p>
{slot.date && (
<p className={`text-xs mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatShortDate(slot.date)}
</p>
)}
</div> </div>
); );
})} })}
@ -416,128 +426,6 @@ export default function AppointmentDetailPage() {
</div> </div>
)} )}
{/* Matching Slots */}
{(() => {
// Check if matching_availability is a MatchingAvailability object with matching_slots
const matchingAvailability = appointment.matching_availability as any;
const hasMatchingSlots = matchingAvailability && matchingAvailability.matching_slots && Array.isArray(matchingAvailability.matching_slots) && matchingAvailability.matching_slots.length > 0;
const isArrayFormat = Array.isArray(matchingAvailability) && matchingAvailability.length > 0;
if (!hasMatchingSlots && !isArrayFormat) return null;
const dayNames: Record<number, string> = {
0: "Monday",
1: "Tuesday",
2: "Wednesday",
3: "Thursday",
4: "Friday",
5: "Saturday",
6: "Sunday",
};
const timeSlotLabels: Record<string, string> = {
morning: "Morning",
afternoon: "Lunchtime",
evening: "Evening",
};
// Get matching slots from MatchingAvailability object
const matchingSlots = hasMatchingSlots ? matchingAvailability.matching_slots : null;
const totalMatchingSlots = hasMatchingSlots ? matchingAvailability.total_matching_slots : null;
const preferencesMatch = hasMatchingSlots ? matchingAvailability.preferences_match_availability : appointment.are_preferences_available;
return (
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
<div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}>
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}>
<CalendarCheck className={`w-5 h-5 ${isDark ? "text-green-400" : "text-green-600"}`} />
Matching Slots
{preferencesMatch !== undefined && (
<span className={`ml-auto px-3 py-1 text-xs font-medium rounded-full ${preferencesMatch ? (isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200") : (isDark ? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30" : "bg-yellow-50 text-yellow-700 border border-yellow-200")}`}>
{preferencesMatch ? "All Available" : "Partially Available"}
</span>
)}
</h2>
</div>
<div className="p-6">
{hasMatchingSlots && totalMatchingSlots && (
<p className={`text-sm mb-4 ${isDark ? "text-gray-400" : "text-gray-600"}`}>
Found {totalMatchingSlots} matching time slot{totalMatchingSlots !== 1 ? 's' : ''} that match your selected preferences:
</p>
)}
{!hasMatchingSlots && (
<p className={`text-sm mb-4 ${isDark ? "text-gray-400" : "text-gray-600"}`}>
These are the available time slots that match your selected preferences:
</p>
)}
{hasMatchingSlots ? (
// Display matching_slots from MatchingAvailability object
<div className="flex flex-wrap gap-3">
{matchingSlots.map((slot: any, idx: number) => {
const dayName = dayNames[slot.day] || `Day ${slot.day}`;
const timeSlot = String(slot.time_slot).toLowerCase().trim();
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
return (
<div
key={idx}
className={`px-4 py-3 rounded-xl border ${isDark ? "bg-green-500/10 border-green-500/30" : "bg-green-50 border-green-200"}`}
>
<p className={`text-sm font-semibold ${isDark ? "text-green-300" : "text-green-700"}`}>
{dayName}
</p>
<p className={`text-xs mt-1 ${isDark ? "text-green-400" : "text-green-600"}`}>
{timeLabel}
</p>
<p className={`text-xs mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatShortDate(slot.date)}
</p>
</div>
);
})}
</div>
) : (
// Display array format (legacy)
<div className="space-y-4">
{(matchingAvailability as any[]).map((match: any, idx: number) => (
<div
key={idx}
className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}
>
<div className="flex items-start justify-between mb-3">
<div>
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
{match.day_name || "Unknown Day"}
</p>
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatShortDate(match.date || match.date_obj || "")}
</p>
</div>
</div>
{match.available_slots && Array.isArray(match.available_slots) && match.available_slots.length > 0 && (
<div className="flex flex-wrap gap-2 mt-3">
{match.available_slots.map((slot: string, slotIdx: number) => {
const normalizedSlot = String(slot).toLowerCase().trim();
return (
<span
key={slotIdx}
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"}`}
>
{timeSlotLabels[normalizedSlot] || slot}
</span>
);
})}
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
})()}
{/* Reason */} {/* Reason */}
{appointment.reason && ( {appointment.reason && (
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}> <div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>

View File

@ -306,13 +306,18 @@ export default function UserAppointmentDetailPage() {
</div> </div>
)} )}
{/* Selected Slots */} {/* Selected Slots (replacing Matching Slots) */}
{appointment.selected_slots && Array.isArray(appointment.selected_slots) && appointment.selected_slots.length > 0 && ( {appointment.selected_slots && Array.isArray(appointment.selected_slots) && appointment.selected_slots.length > 0 && (
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}> <div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
<div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}> <div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}>
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}> <h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}>
<CalendarCheck className={`w-5 h-5 ${isDark ? "text-blue-400" : "text-blue-600"}`} /> <CalendarCheck className={`w-5 h-5 ${isDark ? "text-green-400" : "text-green-600"}`} />
Selected Time Slots Selected Time Slots
{appointment.are_preferences_available !== undefined && (
<span className={`ml-auto px-3 py-1 text-xs font-medium rounded-full ${appointment.are_preferences_available ? (isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200") : (isDark ? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30" : "bg-yellow-50 text-yellow-700 border border-yellow-200")}`}>
{appointment.are_preferences_available ? "All Available" : "Partially Available"}
</span>
)}
</h2> </h2>
</div> </div>
<div className="p-6"> <div className="p-6">
@ -339,14 +344,19 @@ export default function UserAppointmentDetailPage() {
return ( return (
<div <div
key={idx} key={idx}
className={`px-4 py-3 rounded-xl border ${isDark ? "bg-blue-500/10 border-blue-500/30" : "bg-blue-50 border-blue-200"}`} className={`px-4 py-3 rounded-xl border ${isDark ? "bg-green-500/10 border-green-500/30" : "bg-green-50 border-green-200"}`}
> >
<p className={`text-sm font-semibold ${isDark ? "text-blue-300" : "text-blue-700"}`}> <p className={`text-sm font-semibold ${isDark ? "text-green-300" : "text-green-700"}`}>
{dayName} {dayName}
</p> </p>
<p className={`text-xs mt-1 ${isDark ? "text-blue-400" : "text-blue-600"}`}> <p className={`text-xs mt-1 ${isDark ? "text-green-400" : "text-green-600"}`}>
{timeLabel} {timeLabel}
</p> </p>
{slot.date && (
<p className={`text-xs mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatShortDate(slot.date)}
</p>
)}
</div> </div>
); );
})} })}
@ -355,128 +365,6 @@ export default function UserAppointmentDetailPage() {
</div> </div>
)} )}
{/* Matching Slots */}
{(() => {
// Check if matching_availability is a MatchingAvailability object with matching_slots
const matchingAvailability = appointment.matching_availability as any;
const hasMatchingSlots = matchingAvailability && matchingAvailability.matching_slots && Array.isArray(matchingAvailability.matching_slots) && matchingAvailability.matching_slots.length > 0;
const isArrayFormat = Array.isArray(matchingAvailability) && matchingAvailability.length > 0;
if (!hasMatchingSlots && !isArrayFormat) return null;
const dayNames: Record<number, string> = {
0: "Monday",
1: "Tuesday",
2: "Wednesday",
3: "Thursday",
4: "Friday",
5: "Saturday",
6: "Sunday",
};
const timeSlotLabels: Record<string, string> = {
morning: "Morning",
afternoon: "Lunchtime",
evening: "Evening",
};
// Get matching slots from MatchingAvailability object
const matchingSlots = hasMatchingSlots ? matchingAvailability.matching_slots : null;
const totalMatchingSlots = hasMatchingSlots ? matchingAvailability.total_matching_slots : null;
const preferencesMatch = hasMatchingSlots ? matchingAvailability.preferences_match_availability : appointment.are_preferences_available;
return (
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
<div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}>
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}>
<CalendarCheck className={`w-5 h-5 ${isDark ? "text-green-400" : "text-green-600"}`} />
Matching Slots
{preferencesMatch !== undefined && (
<span className={`ml-auto px-3 py-1 text-xs font-medium rounded-full ${preferencesMatch ? (isDark ? "bg-green-500/20 text-green-300 border border-green-500/30" : "bg-green-50 text-green-700 border border-green-200") : (isDark ? "bg-yellow-500/20 text-yellow-300 border border-yellow-500/30" : "bg-yellow-50 text-yellow-700 border border-yellow-200")}`}>
{preferencesMatch ? "All Available" : "Partially Available"}
</span>
)}
</h2>
</div>
<div className="p-6">
{hasMatchingSlots && totalMatchingSlots && (
<p className={`text-sm mb-4 ${isDark ? "text-gray-400" : "text-gray-600"}`}>
Found {totalMatchingSlots} matching time slot{totalMatchingSlots !== 1 ? 's' : ''} that match your selected preferences:
</p>
)}
{!hasMatchingSlots && (
<p className={`text-sm mb-4 ${isDark ? "text-gray-400" : "text-gray-600"}`}>
These are the available time slots that match your selected preferences:
</p>
)}
{hasMatchingSlots ? (
// Display matching_slots from MatchingAvailability object
<div className="flex flex-wrap gap-3">
{matchingSlots.map((slot: any, idx: number) => {
const dayName = dayNames[slot.day] || `Day ${slot.day}`;
const timeSlot = String(slot.time_slot).toLowerCase().trim();
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
return (
<div
key={idx}
className={`px-4 py-3 rounded-xl border ${isDark ? "bg-green-500/10 border-green-500/30" : "bg-green-50 border-green-200"}`}
>
<p className={`text-sm font-semibold ${isDark ? "text-green-300" : "text-green-700"}`}>
{dayName}
</p>
<p className={`text-xs mt-1 ${isDark ? "text-green-400" : "text-green-600"}`}>
{timeLabel}
</p>
<p className={`text-xs mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatShortDate(slot.date)}
</p>
</div>
);
})}
</div>
) : (
// Display array format (legacy)
<div className="space-y-4">
{(matchingAvailability as any[]).map((match: any, idx: number) => (
<div
key={idx}
className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}
>
<div className="flex items-start justify-between mb-3">
<div>
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
{match.day_name || "Unknown Day"}
</p>
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatShortDate(match.date || match.date_obj || "")}
</p>
</div>
</div>
{match.available_slots && Array.isArray(match.available_slots) && match.available_slots.length > 0 && (
<div className="flex flex-wrap gap-2 mt-3">
{match.available_slots.map((slot: string, slotIdx: number) => {
const normalizedSlot = String(slot).toLowerCase().trim();
return (
<span
key={slotIdx}
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"}`}
>
{timeSlotLabels[normalizedSlot] || slot}
</span>
);
})}
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
})()}
{/* Reason */} {/* Reason */}
{appointment.reason && ( {appointment.reason && (
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}> <div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>