Compare commits
2 Commits
ace90b9e10
...
13bc135e09
| Author | SHA1 | Date | |
|---|---|---|---|
| 13bc135e09 | |||
|
|
9930789d71 |
@ -20,7 +20,7 @@ import {
|
|||||||
MapPin,
|
MapPin,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
import { useAppTheme } from "@/components/ThemeProvider";
|
||||||
import { getAppointmentDetail, scheduleAppointment, rejectAppointment } from "@/lib/actions/appointments";
|
import { getAppointmentDetail, scheduleAppointment, rejectAppointment, listAppointments } from "@/lib/actions/appointments";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -58,8 +58,23 @@ export default function AppointmentDetailPage() {
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await getAppointmentDetail(appointmentId);
|
// Fetch both detail and list to get selected_slots from list endpoint
|
||||||
setAppointment(data);
|
const [detailData, listData] = await Promise.all([
|
||||||
|
getAppointmentDetail(appointmentId),
|
||||||
|
listAppointments().catch(() => []) // Fallback to empty array if list fails
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Find matching appointment in list to get selected_slots
|
||||||
|
const listAppointment = Array.isArray(listData)
|
||||||
|
? listData.find((apt: Appointment) => apt.id === appointmentId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Merge selected_slots from list into detail data
|
||||||
|
if (listAppointment && listAppointment.selected_slots && Array.isArray(listAppointment.selected_slots) && listAppointment.selected_slots.length > 0) {
|
||||||
|
detailData.selected_slots = listAppointment.selected_slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppointment(detailData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to load appointment details");
|
toast.error("Failed to load appointment details");
|
||||||
router.push("/admin/booking");
|
router.push("/admin/booking");
|
||||||
@ -367,7 +382,7 @@ export default function AppointmentDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Selected Slots (replacing Matching Slots) */}
|
{/* Selected 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"}`}>
|
||||||
@ -382,46 +397,65 @@ export default function AppointmentDetailPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex flex-wrap gap-3">
|
{(() => {
|
||||||
{appointment.selected_slots.map((slot: any, idx: number) => {
|
const dayNames: Record<number, string> = {
|
||||||
const dayNames: Record<number, string> = {
|
0: "Monday",
|
||||||
0: "Monday",
|
1: "Tuesday",
|
||||||
1: "Tuesday",
|
2: "Wednesday",
|
||||||
2: "Wednesday",
|
3: "Thursday",
|
||||||
3: "Thursday",
|
4: "Friday",
|
||||||
4: "Friday",
|
5: "Saturday",
|
||||||
5: "Saturday",
|
6: "Sunday",
|
||||||
6: "Sunday",
|
};
|
||||||
};
|
const timeSlotLabels: Record<string, string> = {
|
||||||
const timeSlotLabels: Record<string, string> = {
|
morning: "Morning",
|
||||||
morning: "Morning",
|
afternoon: "Lunchtime",
|
||||||
afternoon: "Lunchtime",
|
evening: "Evening",
|
||||||
evening: "Evening",
|
};
|
||||||
};
|
|
||||||
const dayName = dayNames[slot.day] || `Day ${slot.day}`;
|
|
||||||
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
|
||||||
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
|
||||||
|
|
||||||
return (
|
// Group slots by date
|
||||||
<div
|
const slotsByDate: Record<string, typeof appointment.selected_slots> = {};
|
||||||
key={idx}
|
appointment.selected_slots.forEach((slot: any) => {
|
||||||
className={`px-4 py-3 rounded-xl border ${isDark ? "bg-green-500/10 border-green-500/30" : "bg-green-50 border-green-200"}`}
|
const date = slot.date || "";
|
||||||
>
|
if (!slotsByDate[date]) {
|
||||||
<p className={`text-sm font-semibold ${isDark ? "text-green-300" : "text-green-700"}`}>
|
slotsByDate[date] = [];
|
||||||
{dayName}
|
}
|
||||||
</p>
|
slotsByDate[date].push(slot);
|
||||||
<p className={`text-xs mt-1 ${isDark ? "text-green-400" : "text-green-600"}`}>
|
});
|
||||||
{timeLabel}
|
|
||||||
</p>
|
return (
|
||||||
{slot.date && (
|
<div className="space-y-4">
|
||||||
<p className={`text-xs mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
{Object.entries(slotsByDate).map(([date, slots]) => (
|
||||||
{formatShortDate(slot.date)}
|
<div key={date} className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||||||
</p>
|
<div className="mb-3">
|
||||||
)}
|
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||||
</div>
|
{formatShortDate(date)}
|
||||||
);
|
</p>
|
||||||
})}
|
{slots.length > 0 && slots[0]?.day !== undefined && (
|
||||||
</div>
|
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||||
|
{dayNames[slots[0].day] || `Day ${slots[0].day}`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{slots.map((slot: any, idx: number) => {
|
||||||
|
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
||||||
|
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
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"}`}
|
||||||
|
>
|
||||||
|
{timeLabel}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -680,27 +680,52 @@ export default function Booking() {
|
|||||||
</td>
|
</td>
|
||||||
<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"}`}>
|
<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"}`}>
|
||||||
{(() => {
|
{(() => {
|
||||||
// Handle preferred_dates
|
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",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show selected_slots if available
|
||||||
|
if (appointment.selected_slots && Array.isArray(appointment.selected_slots) && appointment.selected_slots.length > 0) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{appointment.selected_slots.slice(0, 2).map((slot, idx) => (
|
||||||
|
<span key={idx} className="text-xs sm:text-sm">
|
||||||
|
{dayNames[slot.day] || `Day ${slot.day}`} - {timeSlotLabels[slot.time_slot] || slot.time_slot}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{appointment.selected_slots.length > 2 && (
|
||||||
|
<span className="text-xs opacity-75">
|
||||||
|
+{appointment.selected_slots.length - 2} more
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to preferred_dates and preferred_time_slots if selected_slots not available
|
||||||
const dates = Array.isArray(appointment.preferred_dates)
|
const dates = Array.isArray(appointment.preferred_dates)
|
||||||
? appointment.preferred_dates
|
? appointment.preferred_dates
|
||||||
: appointment.preferred_dates
|
: appointment.preferred_dates
|
||||||
? [appointment.preferred_dates]
|
? [appointment.preferred_dates]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Handle preferred_time_slots
|
|
||||||
const timeSlots = Array.isArray(appointment.preferred_time_slots)
|
const timeSlots = Array.isArray(appointment.preferred_time_slots)
|
||||||
? appointment.preferred_time_slots
|
? appointment.preferred_time_slots
|
||||||
: appointment.preferred_time_slots
|
: appointment.preferred_time_slots
|
||||||
? [appointment.preferred_time_slots]
|
? [appointment.preferred_time_slots]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Time slot labels
|
|
||||||
const timeSlotLabels: Record<string, string> = {
|
|
||||||
morning: "Morning",
|
|
||||||
afternoon: "Lunchtime",
|
|
||||||
evening: "Evening",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dates.length === 0 && timeSlots.length === 0) {
|
if (dates.length === 0 && timeSlots.length === 0) {
|
||||||
return <span>-</span>;
|
return <span>-</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
Copy,
|
Copy,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
import { useAppTheme } from "@/components/ThemeProvider";
|
||||||
import { getAppointmentDetail } from "@/lib/actions/appointments";
|
import { getAppointmentDetail, listAppointments } from "@/lib/actions/appointments";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Navbar } from "@/components/Navbar";
|
import { Navbar } from "@/components/Navbar";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -40,8 +40,23 @@ export default function UserAppointmentDetailPage() {
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await getAppointmentDetail(appointmentId);
|
// Fetch both detail and list to get selected_slots from list endpoint
|
||||||
setAppointment(data);
|
const [detailData, listData] = await Promise.all([
|
||||||
|
getAppointmentDetail(appointmentId),
|
||||||
|
listAppointments().catch(() => []) // Fallback to empty array if list fails
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Find matching appointment in list to get selected_slots
|
||||||
|
const listAppointment = Array.isArray(listData)
|
||||||
|
? listData.find((apt: Appointment) => apt.id === appointmentId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Merge selected_slots from list into detail data
|
||||||
|
if (listAppointment && listAppointment.selected_slots && Array.isArray(listAppointment.selected_slots) && listAppointment.selected_slots.length > 0) {
|
||||||
|
detailData.selected_slots = listAppointment.selected_slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppointment(detailData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to load appointment details");
|
toast.error("Failed to load appointment details");
|
||||||
router.push("/user/dashboard");
|
router.push("/user/dashboard");
|
||||||
@ -306,7 +321,7 @@ export default function UserAppointmentDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Selected Slots (replacing Matching Slots) */}
|
{/* Selected 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"}`}>
|
||||||
@ -321,46 +336,65 @@ export default function UserAppointmentDetailPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex flex-wrap gap-3">
|
{(() => {
|
||||||
{appointment.selected_slots.map((slot: any, idx: number) => {
|
const dayNames: Record<number, string> = {
|
||||||
const dayNames: Record<number, string> = {
|
0: "Monday",
|
||||||
0: "Monday",
|
1: "Tuesday",
|
||||||
1: "Tuesday",
|
2: "Wednesday",
|
||||||
2: "Wednesday",
|
3: "Thursday",
|
||||||
3: "Thursday",
|
4: "Friday",
|
||||||
4: "Friday",
|
5: "Saturday",
|
||||||
5: "Saturday",
|
6: "Sunday",
|
||||||
6: "Sunday",
|
};
|
||||||
};
|
const timeSlotLabels: Record<string, string> = {
|
||||||
const timeSlotLabels: Record<string, string> = {
|
morning: "Morning",
|
||||||
morning: "Morning",
|
afternoon: "Lunchtime",
|
||||||
afternoon: "Lunchtime",
|
evening: "Evening",
|
||||||
evening: "Evening",
|
};
|
||||||
};
|
|
||||||
const dayName = dayNames[slot.day] || `Day ${slot.day}`;
|
|
||||||
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
|
||||||
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
|
||||||
|
|
||||||
return (
|
// Group slots by date
|
||||||
<div
|
const slotsByDate: Record<string, typeof appointment.selected_slots> = {};
|
||||||
key={idx}
|
appointment.selected_slots.forEach((slot: any) => {
|
||||||
className={`px-4 py-3 rounded-xl border ${isDark ? "bg-green-500/10 border-green-500/30" : "bg-green-50 border-green-200"}`}
|
const date = slot.date || "";
|
||||||
>
|
if (!slotsByDate[date]) {
|
||||||
<p className={`text-sm font-semibold ${isDark ? "text-green-300" : "text-green-700"}`}>
|
slotsByDate[date] = [];
|
||||||
{dayName}
|
}
|
||||||
</p>
|
slotsByDate[date].push(slot);
|
||||||
<p className={`text-xs mt-1 ${isDark ? "text-green-400" : "text-green-600"}`}>
|
});
|
||||||
{timeLabel}
|
|
||||||
</p>
|
return (
|
||||||
{slot.date && (
|
<div className="space-y-4">
|
||||||
<p className={`text-xs mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
{Object.entries(slotsByDate).map(([date, slots]) => (
|
||||||
{formatShortDate(slot.date)}
|
<div key={date} className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||||||
</p>
|
<div className="mb-3">
|
||||||
)}
|
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||||
</div>
|
{formatShortDate(date)}
|
||||||
);
|
</p>
|
||||||
})}
|
{slots.length > 0 && slots[0]?.day !== undefined && (
|
||||||
</div>
|
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||||
|
{dayNames[slots[0].day] || `Day ${slots[0].day}`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{slots.map((slot: any, idx: number) => {
|
||||||
|
const timeSlot = String(slot.time_slot).toLowerCase().trim();
|
||||||
|
const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
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"}`}
|
||||||
|
>
|
||||||
|
{timeLabel}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user