Refactor Booking component to improve rendering of preferred availability and time slots. Update appointment model to support both string and array formats for preferred_dates and preferred_time_slots, enhancing flexibility in data handling. #39

Merged
Hammond merged 1 commits from feat/booking-panel into master 2025-12-01 19:50:05 +00:00
2 changed files with 68 additions and 22 deletions
Showing only changes of commit 80882c80bc - Show all commits

View File

@ -607,7 +607,7 @@ export default function Booking() {
Status Status
</th> </th>
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}> <th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Preferred Dates Preferred Availability
</th> </th>
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden xl:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}> <th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden xl:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
Created Created
@ -678,25 +678,71 @@ export default function Booking() {
{formatStatus(appointment.status)} {formatStatus(appointment.status)}
</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 text-xs sm:text-sm hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{appointment.preferred_dates ? ( {(() => {
<div className="flex flex-col gap-1"> // Handle preferred_dates
{Array.isArray(appointment.preferred_dates) ? ( const dates = Array.isArray(appointment.preferred_dates)
<> ? appointment.preferred_dates
{(appointment.preferred_dates as string[]).slice(0, 2).map((date, idx) => ( : appointment.preferred_dates
<span key={idx}>{formatDate(date)}</span> ? [appointment.preferred_dates]
))} : [];
{appointment.preferred_dates.length > 2 && (
<span className="text-xs">+{appointment.preferred_dates.length - 2} more</span> // Handle preferred_time_slots
)} const timeSlots = Array.isArray(appointment.preferred_time_slots)
</> ? appointment.preferred_time_slots
) : ( : appointment.preferred_time_slots
<span>{appointment.preferred_dates_display || appointment.preferred_dates}</span> ? [appointment.preferred_time_slots]
)} : [];
</div>
) : ( // Time slot labels
"-" const timeSlotLabels: Record<string, string> = {
)} morning: "Morning",
afternoon: "Lunchtime",
evening: "Evening",
};
if (dates.length === 0 && timeSlots.length === 0) {
return <span>-</span>;
}
return (
<div className="flex flex-col gap-1.5">
{dates.length > 0 && (
<div className="flex flex-col gap-0.5">
{dates.slice(0, 2).map((date, idx) => (
<span key={idx} className="font-medium">
{formatDate(date)}
</span>
))}
{dates.length > 2 && (
<span className="text-xs opacity-75">
+{dates.length - 2} more date{dates.length - 2 > 1 ? 's' : ''}
</span>
)}
</div>
)}
{timeSlots.length > 0 && (
<div className="flex flex-wrap gap-1">
{timeSlots.map((slot, idx) => {
const normalizedSlot = String(slot).toLowerCase().trim();
return (
<span
key={idx}
className={`px-2 py-0.5 rounded text-xs font-medium ${
isDark
? "bg-blue-500/20 text-blue-300 border border-blue-500/30"
: "bg-blue-50 text-blue-700 border border-blue-200"
}`}
>
{timeSlotLabels[normalizedSlot] || slot}
</span>
);
})}
</div>
)}
</div>
);
})()}
</td> </td>
<td className={`px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm hidden xl: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 xl:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
{formatDate(appointment.created_at)} {formatDate(appointment.created_at)}

View File

@ -7,8 +7,8 @@ export interface Appointment {
email: string; email: string;
phone?: string; phone?: string;
reason?: string; reason?: string;
preferred_dates?: string; // YYYY-MM-DD format (legacy) - API returns as string, not array preferred_dates?: string | string[]; // YYYY-MM-DD format - API can return as string or array
preferred_time_slots?: string; // "morning", "afternoon", "evening" (legacy) - API returns as string preferred_time_slots?: string | string[]; // "morning", "afternoon", "evening" - API can return as string or array
selected_slots?: SelectedSlot[]; // New format: day-time combinations selected_slots?: SelectedSlot[]; // New format: day-time combinations
status: "pending_review" | "scheduled" | "rejected" | "completed" | "cancelled"; status: "pending_review" | "scheduled" | "rejected" | "completed" | "cancelled";
created_at: string; created_at: string;