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 */}
{(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={`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"}`}>
@ -376,37 +376,53 @@ export default function AppointmentDetailPage() {
</h2>
</div>
<div className="p-6 space-y-6">
{appointment.preferred_dates && appointment.preferred_dates.length > 0 && (
{appointment.preferred_dates && (
<div>
<p className={`text-sm font-medium mb-3 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Preferred Dates
</p>
<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
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"}`}
>
{formatShortDate(date)}
</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>
)}
{appointment.preferred_time_slots && appointment.preferred_time_slots.length > 0 && (
{appointment.preferred_time_slots && (
<div>
<p className={`text-sm font-medium mb-3 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Preferred Time Slots
</p>
<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
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"}`}
>
{slot}
</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>
)}

View File

@ -200,13 +200,15 @@ export default function Booking() {
try {
// Build availability_schedule format: {"0": ["morning", "evening"], "1": ["afternoon"]}
const availabilitySchedule: Record<string, string[]> = {};
const availabilitySchedule: Record<string, ("morning" | "afternoon" | "evening")[]> = {};
selectedDays.forEach(day => {
const timeSlots = dayTimeSlots[day];
if (timeSlots && timeSlots.length > 0) {
// Ensure only valid time slots and remove duplicates
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
if (validSlots.length > 0) {
@ -677,14 +679,20 @@ export default function Booking() {
</span>
</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"}`}>
{appointment.preferred_dates && appointment.preferred_dates.length > 0 ? (
{appointment.preferred_dates ? (
<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>
))}
{appointment.preferred_dates.length > 2 && (
<span className="text-xs">+{appointment.preferred_dates.length - 2} more</span>
)}
</>
) : (
<span>{appointment.preferred_dates_display || appointment.preferred_dates}</span>
)}
</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"}`}>
<Icon className={`w-4 h-4 sm:w-5 sm:h-5 ${isDark ? "text-gray-200" : "text-gray-600"}`} />
</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)}`}>
{card.trendUp ? (
<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"}`}>
{card.value}
</p>
{card.showTrend !== false && (
<p className={`text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`}>
vs last month
</p>
)}
</div>
</div>
);

View File

@ -138,11 +138,16 @@ export function useAppointments(options?: {
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>({
queryKey: ["appointments", "user", "stats"],
queryFn: () => getUserAppointmentStats(),
enabled: enableStats,
queryFn: async () => {
// 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
});