Refactor Booking and AppointmentDetail components to improve handling of preferred dates and time slots. Enhance type safety by ensuring preferred_dates and preferred_time_slots are validated as arrays. Update rendering logic to handle different data formats for better user experience and consistency.

This commit is contained in:
iamkiddy 2025-11-27 19:33:54 +00:00
parent 6b83b092e3
commit 5556e88fbf
4 changed files with 60 additions and 33 deletions

View File

@ -368,7 +368,7 @@ export default function AppointmentDetailPage() {
)} )}
{/* Preferred Dates & Times */} {/* Preferred Dates & Times */}
{(appointment.preferred_dates?.length > 0 || appointment.preferred_time_slots?.length > 0) && ( {((appointment.preferred_dates && appointment.preferred_dates.length > 0) || (appointment.preferred_time_slots && appointment.preferred_time_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 ${isDark ? "text-white" : "text-gray-900"}`}> <h2 className={`text-lg font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
@ -376,37 +376,53 @@ export default function AppointmentDetailPage() {
</h2> </h2>
</div> </div>
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
{appointment.preferred_dates && appointment.preferred_dates.length > 0 && ( {appointment.preferred_dates && (
<div> <div>
<p className={`text-sm font-medium mb-3 ${isDark ? "text-gray-400" : "text-gray-500"}`}> <p className={`text-sm font-medium mb-3 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Preferred Dates Preferred Dates
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{appointment.preferred_dates.map((date, idx) => ( {Array.isArray(appointment.preferred_dates) ? (
(appointment.preferred_dates as string[]).map((date, idx) => (
<span <span
key={idx} key={idx}
className={`px-4 py-2 rounded-lg text-sm font-medium ${isDark ? "bg-gray-700 text-gray-200 border border-gray-600" : "bg-gray-100 text-gray-700 border border-gray-200"}`} className={`px-4 py-2 rounded-lg text-sm font-medium ${isDark ? "bg-gray-700 text-gray-200 border border-gray-600" : "bg-gray-100 text-gray-700 border border-gray-200"}`}
> >
{formatShortDate(date)} {formatShortDate(date)}
</span> </span>
))} ))
) : (
<span
className={`px-4 py-2 rounded-lg text-sm font-medium ${isDark ? "bg-gray-700 text-gray-200 border border-gray-600" : "bg-gray-100 text-gray-700 border border-gray-200"}`}
>
{appointment.preferred_dates_display || appointment.preferred_dates || 'N/A'}
</span>
)}
</div> </div>
</div> </div>
)} )}
{appointment.preferred_time_slots && appointment.preferred_time_slots.length > 0 && ( {appointment.preferred_time_slots && (
<div> <div>
<p className={`text-sm font-medium mb-3 ${isDark ? "text-gray-400" : "text-gray-500"}`}> <p className={`text-sm font-medium mb-3 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Preferred Time Slots Preferred Time Slots
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{appointment.preferred_time_slots.map((slot, idx) => ( {Array.isArray(appointment.preferred_time_slots) ? (
(appointment.preferred_time_slots as string[]).map((slot, idx) => (
<span <span
key={idx} key={idx}
className={`px-4 py-2 rounded-lg text-sm font-medium capitalize ${isDark ? "bg-rose-500/20 text-rose-300 border border-rose-500/30" : "bg-rose-50 text-rose-700 border border-rose-200"}`} className={`px-4 py-2 rounded-lg text-sm font-medium capitalize ${isDark ? "bg-rose-500/20 text-rose-300 border border-rose-500/30" : "bg-rose-50 text-rose-700 border border-rose-200"}`}
> >
{slot} {slot}
</span> </span>
))} ))
) : (
<span
className={`px-4 py-2 rounded-lg text-sm font-medium capitalize ${isDark ? "bg-rose-500/20 text-rose-300 border border-rose-500/30" : "bg-rose-50 text-rose-700 border border-rose-200"}`}
>
{appointment.preferred_time_slots_display || appointment.preferred_time_slots || 'N/A'}
</span>
)}
</div> </div>
</div> </div>
)} )}

View File

@ -200,13 +200,15 @@ export default function Booking() {
try { try {
// Build availability_schedule format: {"0": ["morning", "evening"], "1": ["afternoon"]} // Build availability_schedule format: {"0": ["morning", "evening"], "1": ["afternoon"]}
const availabilitySchedule: Record<string, string[]> = {}; const availabilitySchedule: Record<string, ("morning" | "afternoon" | "evening")[]> = {};
selectedDays.forEach(day => { selectedDays.forEach(day => {
const timeSlots = dayTimeSlots[day]; const timeSlots = dayTimeSlots[day];
if (timeSlots && timeSlots.length > 0) { if (timeSlots && timeSlots.length > 0) {
// Ensure only valid time slots and remove duplicates // Ensure only valid time slots and remove duplicates
const validSlots = timeSlots const validSlots = timeSlots
.filter(slot => ["morning", "afternoon", "evening"].includes(slot)) .filter((slot): slot is "morning" | "afternoon" | "evening" =>
["morning", "afternoon", "evening"].includes(slot)
)
.filter((slot, index, self) => self.indexOf(slot) === index); // Remove duplicates .filter((slot, index, self) => self.indexOf(slot) === index); // Remove duplicates
if (validSlots.length > 0) { if (validSlots.length > 0) {
@ -677,14 +679,20 @@ export default function Booking() {
</span> </span>
</td> </td>
<td className={`px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}> <td className={`px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{appointment.preferred_dates && appointment.preferred_dates.length > 0 ? ( {appointment.preferred_dates ? (
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{appointment.preferred_dates.slice(0, 2).map((date, idx) => ( {Array.isArray(appointment.preferred_dates) ? (
<>
{(appointment.preferred_dates as string[]).slice(0, 2).map((date, idx) => (
<span key={idx}>{formatDate(date)}</span> <span key={idx}>{formatDate(date)}</span>
))} ))}
{appointment.preferred_dates.length > 2 && ( {appointment.preferred_dates.length > 2 && (
<span className="text-xs">+{appointment.preferred_dates.length - 2} more</span> <span className="text-xs">+{appointment.preferred_dates.length - 2} more</span>
)} )}
</>
) : (
<span>{appointment.preferred_dates_display || appointment.preferred_dates}</span>
)}
</div> </div>
) : ( ) : (
"-" "-"

View File

@ -272,7 +272,7 @@ export default function Dashboard() {
<div className={`p-2 sm:p-2.5 rounded-lg ${isDark ? "bg-gray-700" : "bg-gray-50"}`}> <div className={`p-2 sm:p-2.5 rounded-lg ${isDark ? "bg-gray-700" : "bg-gray-50"}`}>
<Icon className={`w-4 h-4 sm:w-5 sm:h-5 ${isDark ? "text-gray-200" : "text-gray-600"}`} /> <Icon className={`w-4 h-4 sm:w-5 sm:h-5 ${isDark ? "text-gray-200" : "text-gray-600"}`} />
</div> </div>
{card.showTrend !== false && card.trend && ( {card.trend && (
<div className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium ${getTrendClasses(card.trendUp)}`}> <div className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium ${getTrendClasses(card.trendUp)}`}>
{card.trendUp ? ( {card.trendUp ? (
<ArrowUpRight className="w-3 h-3" /> <ArrowUpRight className="w-3 h-3" />
@ -291,11 +291,9 @@ export default function Dashboard() {
<p className={`text-xl sm:text-2xl font-bold mb-1 ${isDark ? "text-white" : "text-gray-900"}`}> <p className={`text-xl sm:text-2xl font-bold mb-1 ${isDark ? "text-white" : "text-gray-900"}`}>
{card.value} {card.value}
</p> </p>
{card.showTrend !== false && (
<p className={`text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`}> <p className={`text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`}>
vs last month vs last month
</p> </p>
)}
</div> </div>
</div> </div>
); );

View File

@ -138,11 +138,16 @@ export function useAppointments(options?: {
staleTime: 1 * 60 * 1000, // 1 minute staleTime: 1 * 60 * 1000, // 1 minute
}); });
// Get user appointment stats query // Get user appointment stats query - disabled because it requires email parameter
// Use getUserAppointmentStats(email) directly where email is available
const userAppointmentStatsQuery = useQuery<UserAppointmentStats>({ const userAppointmentStatsQuery = useQuery<UserAppointmentStats>({
queryKey: ["appointments", "user", "stats"], queryKey: ["appointments", "user", "stats"],
queryFn: () => getUserAppointmentStats(), queryFn: async () => {
enabled: enableStats, // This query is disabled - getUserAppointmentStats requires email parameter
// Use getUserAppointmentStats(email) directly in components where email is available
return {} as UserAppointmentStats;
},
enabled: false, // Disabled - requires email parameter which hook doesn't have access to
staleTime: 1 * 60 * 1000, // 1 minute staleTime: 1 * 60 * 1000, // 1 minute
}); });