fix: consulting slots display and auth reload redirect

- Group consulting slots by date in admin order detail modal
- Show time range from first slot start to last slot end
- Display session count badge for multi-slot orders
- Fix page reload redirecting to main page by ensuring loading state
  is properly synchronized with Supabase session initialization
- Add mounted flag to prevent state updates after unmount

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-27 23:47:53 +07:00
parent 777d989d34
commit 79e1bd82fc
2 changed files with 131 additions and 93 deletions

View File

@@ -508,87 +508,110 @@ export default function AdminOrders() {
</div>
)}
{/* Consulting Slots */}
{consultingSlots.length > 0 && (
<div className="border-t border-border pt-4">
<p className="font-medium mb-3 flex items-center gap-2">
<Video className="w-4 h-4" />
Jadwal Konsultasi
</p>
<div className="space-y-2">
{consultingSlots.map((slot) => (
<div key={slot.id} className="border-2 border-border rounded-lg p-3 bg-background">
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<Badge variant={slot.status === "confirmed" ? "default" : "secondary"} className="text-xs">
{slot.status === "confirmed" ? "Terkonfirmasi" : slot.status}
</Badge>
{slot.topic_category && (
<Badge variant="outline" className="text-xs">
{slot.topic_category}
</Badge>
)}
{/* Meet Link Status */}
{slot.meet_link ? (
<Badge variant="outline" className="text-xs gap-1 border-green-500 text-green-700">
<CheckCircle className="w-3 h-3" />
Meet Link Ready
</Badge>
) : (
<Badge variant="outline" className="text-xs gap-1 border-amber-500 text-amber-700">
<AlertTriangle className="w-3 h-3" />
Belum ada Meet Link
</Badge>
)}
</div>
<p className="text-sm font-medium">
{new Date(slot.date).toLocaleDateString("id-ID", {
weekday: "short",
day: "numeric",
month: "short",
year: "numeric"
})}
</p>
<p className="text-xs text-muted-foreground">
{slot.start_time.substring(0, 5)} - {slot.end_time.substring(0, 5)} WIB
</p>
{slot.notes && (
<p className="text-xs text-muted-foreground mt-1 italic">
Catatan: {slot.notes}
</p>
)}
</div>
<div className="flex gap-2">
{slot.meet_link && (
<Button asChild variant="outline" size="sm" className="gap-1">
<a
href={slot.meet_link}
target="_blank"
rel="noopener noreferrer"
{/* Consulting Slots - Grouped by Date */}
{consultingSlots.length > 0 && (() => {
// Group slots by date
const slotsByDate = consultingSlots.reduce((acc, slot) => {
if (!acc[slot.date]) {
acc[slot.date] = [];
}
acc[slot.date].push(slot);
return acc;
}, {} as Record<string, typeof consultingSlots>);
return (
<div className="border-t border-border pt-4">
<p className="font-medium mb-3 flex items-center gap-2">
<Video className="w-4 h-4" />
Jadwal Konsultasi
</p>
<div className="space-y-3">
{Object.entries(slotsByDate).map(([date, slots]) => {
const firstSlot = slots[0];
const lastSlot = slots[slots.length - 1];
const allSlotsHaveMeetLink = slots.every(s => s.meet_link);
const meetLink = firstSlot.meet_link;
return (
<div key={date} className="border-2 border-border rounded-lg p-3 bg-background">
<div className="flex items-start justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<Badge variant={firstSlot.status === "confirmed" ? "default" : "secondary"} className="text-xs">
{firstSlot.status === "confirmed" ? "Terkonfirmasi" : firstSlot.status}
</Badge>
{firstSlot.topic_category && (
<Badge variant="outline" className="text-xs">
{firstSlot.topic_category}
</Badge>
)}
{slots.length > 1 && (
<Badge variant="outline" className="text-xs">
{slots.length} sesi
</Badge>
)}
{/* Meet Link Status */}
{allSlotsHaveMeetLink ? (
<Badge variant="outline" className="text-xs gap-1 border-green-500 text-green-700">
<CheckCircle className="w-3 h-3" />
Meet Link Ready
</Badge>
) : (
<Badge variant="outline" className="text-xs gap-1 border-amber-500 text-amber-700">
<AlertTriangle className="w-3 h-3" />
Belum ada Meet Link
</Badge>
)}
</div>
<p className="text-sm font-medium">
{new Date(date).toLocaleDateString("id-ID", {
weekday: "short",
day: "numeric",
month: "short",
year: "numeric"
})}
</p>
<p className="text-xs text-muted-foreground">
{firstSlot.start_time.substring(0, 5)} - {lastSlot.end_time.substring(0, 5)} WIB
</p>
{firstSlot.notes && (
<p className="text-xs text-muted-foreground mt-1 italic">
Catatan: {firstSlot.notes}
</p>
)}
</div>
<div className="flex gap-2">
{meetLink && (
<Button asChild variant="outline" size="sm" className="gap-1">
<a
href={meetLink}
target="_blank"
rel="noopener noreferrer"
>
<Video className="w-3 h-3" />
Meet
<ExternalLink className="w-3 h-3" />
</a>
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={() => openMeetLinkDialog(firstSlot.id, meetLink)}
className="gap-1"
>
<Video className="w-3 h-3" />
Meet
<ExternalLink className="w-3 h-3" />
</a>
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={() => openMeetLinkDialog(slot.id, slot.meet_link)}
className="gap-1"
>
<LinkIcon className="w-3 h-3" />
{slot.meet_link ? "Update" : "Buat"} Link
</Button>
<LinkIcon className="w-3 h-3" />
{meetLink ? "Update" : "Buat"} Link
</Button>
</div>
</div>
</div>
</div>
</div>
))}
);
})}
</div>
</div>
</div>
)}
);
})()}
<div className="flex flex-col sm:flex-row gap-2 pt-4">
{canRefundOrder(selectedOrder.payment_status, selectedOrder.refunded_at) && (