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:
parent
6b83b092e3
commit
5556e88fbf
@ -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>
|
||||||
)}
|
)}
|
||||||
@ -635,15 +651,15 @@ export default function AppointmentDetailPage() {
|
|||||||
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gradient-to-br from-blue-900/20 to-purple-900/20 border-blue-800/30" : "bg-gradient-to-br from-blue-50 to-purple-50 border-blue-200"}`}>
|
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gradient-to-br from-blue-900/20 to-purple-900/20 border-blue-800/30" : "bg-gradient-to-br from-blue-50 to-purple-50 border-blue-200"}`}>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
{appointment.can_join_meeting ? (
|
{appointment.can_join_meeting ? (
|
||||||
<a
|
<a
|
||||||
href={appointment.jitsi_meet_url}
|
href={appointment.jitsi_meet_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={`flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 text-white h-12 rounded-lg text-base font-medium transition-colors`}
|
className={`flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 text-white h-12 rounded-lg text-base font-medium transition-colors`}
|
||||||
>
|
>
|
||||||
<Video className="w-5 h-5" />
|
<Video className="w-5 h-5" />
|
||||||
Join Meeting
|
Join Meeting
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
disabled
|
disabled
|
||||||
|
|||||||
@ -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,13 +679,19 @@ 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) ? (
|
||||||
<span key={idx}>{formatDate(date)}</span>
|
<>
|
||||||
))}
|
{(appointment.preferred_dates as string[]).slice(0, 2).map((date, idx) => (
|
||||||
{appointment.preferred_dates.length > 2 && (
|
<span key={idx}>{formatDate(date)}</span>
|
||||||
<span className="text-xs">+{appointment.preferred_dates.length - 2} more</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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user