From 0be27ccf9990d15708595330c6ee9923ace3c5d2 Mon Sep 17 00:00:00 2001 From: dwindown Date: Wed, 31 Dec 2025 12:59:07 +0700 Subject: [PATCH] Handle passed consulting sessions for admin and member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Passed consulting sessions stayed in "Dikonfirmasi" status indefinitely, showing JOIN button to members even after session ended, with no admin action buttons. Solution: 1. Admin UI (AdminConsulting.tsx): - Add isSessionPassed() helper to check if session end time has passed - Add "Sesi Terlewat" alert card at top with quick action buttons - Add "Perlu Update" stat card (orange) for passed confirmed sessions - Existing action buttons (Selesai/Batal) already work for confirmed sessions 2. Member UI (ConsultingHistory.tsx): - Move passed confirmed sessions from "Sesi Mendatang" to new "Sesi Terlewat" section - Remove JOIN button for passed sessions - Show "Menunggu konfirmasi admin" status message - Display with orange styling to indicate needs attention 3. Order Detail (OrderDetail.tsx): - Add isConsultingSessionPassed check - Show orange alert for passed paid sessions: "Sesi telah berakhir. Menunggu konfirmasi admin" - Keep green alert for upcoming paid sessions Flow: - Session ends → Still shows "confirmed" status - Admin sees orange alert → Clicks Selesai or Batal - Member sees passed session → No JOIN button → Waits for admin - Admin updates status → Session moves to completed/cancelled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/components/reviews/ConsultingHistory.tsx | 36 ++++++++++++- src/pages/admin/AdminConsulting.tsx | 57 ++++++++++++++++++-- src/pages/member/OrderDetail.tsx | 28 +++++++--- 3 files changed, 111 insertions(+), 10 deletions(-) diff --git a/src/components/reviews/ConsultingHistory.tsx b/src/components/reviews/ConsultingHistory.tsx index 739756b..938cbd1 100644 --- a/src/components/reviews/ConsultingHistory.tsx +++ b/src/components/reviews/ConsultingHistory.tsx @@ -87,6 +87,12 @@ export function ConsultingHistory({ userId }: ConsultingHistoryProps) { } }; + // Check if session has passed + const isSessionPassed = (session: ConsultingSession) => { + const sessionEndDateTime = new Date(`${session.session_date}T${session.end_time}`); + return new Date() > sessionEndDateTime; + }; + const openReviewModal = (session: ConsultingSession) => { const dateLabel = format(new Date(session.session_date), 'd MMMM yyyy', { locale: id }); const timeLabel = `${session.start_time.substring(0, 5)} - ${session.end_time.substring(0, 5)}`; @@ -128,7 +134,8 @@ export function ConsultingHistory({ userId }: ConsultingHistoryProps) { }; const doneSessions = sessions.filter(s => s.status === 'done' || s.status === 'completed'); - const upcomingSessions = sessions.filter(s => s.status === 'confirmed'); + const upcomingSessions = sessions.filter(s => s.status === 'confirmed' && !isSessionPassed(s)); + const passedSessions = sessions.filter(s => s.status === 'confirmed' && isSessionPassed(s)); if (loading) { return ( @@ -212,6 +219,33 @@ export function ConsultingHistory({ userId }: ConsultingHistoryProps) { )} + {/* Passed confirmed sessions (waiting for admin action) */} + {passedSessions.length > 0 && ( +
+

Sesi Terlewat

+ {passedSessions.map((session) => ( +
+
+ +
+

+ {format(new Date(session.session_date), 'd MMM yyyy', { locale: id })} +

+

+ {session.start_time.substring(0, 5)} - {session.end_time.substring(0, 5)} + {session.topic_category && ` • ${session.topic_category}`} +

+
+
+
+ {getStatusBadge(session.status)} + Menunggu konfirmasi admin +
+
+ ))} +
+ )} + {/* Completed sessions */} {doneSessions.length > 0 && (
diff --git a/src/pages/admin/AdminConsulting.tsx b/src/pages/admin/AdminConsulting.tsx index 813ed38..055968d 100644 --- a/src/pages/admin/AdminConsulting.tsx +++ b/src/pages/admin/AdminConsulting.tsx @@ -323,6 +323,12 @@ export default function AdminConsulting() { } }; + // Helper function to check if session has passed + const isSessionPassed = (session: ConsultingSession) => { + const sessionEndDateTime = new Date(`${session.session_date}T${session.end_time}`); + return new Date() > sessionEndDateTime; + }; + const updateSessionStatus = async (sessionId: string, newStatus: string) => { // If cancelling and session has a calendar event, delete it first if (newStatus === 'cancelled') { @@ -389,6 +395,7 @@ export default function AdminConsulting() { const upcomingSessions = filteredSessions.filter(s => s.session_date >= today && (s.status === 'confirmed' || s.status === 'pending_payment')); const pastSessions = filteredSessions.filter(s => s.session_date < today || s.status === 'completed' || s.status === 'cancelled'); const todaySessions = filteredSessions.filter(s => isToday(parseISO(s.session_date)) && s.status === 'confirmed'); + const passedConfirmedSessions = filteredSessions.filter(s => s.status === 'confirmed' && isSessionPassed(s)); return ( @@ -401,6 +408,50 @@ export default function AdminConsulting() { Kelola jadwal dan link Google Meet untuk sesi konsultasi

+ {/* Passed Confirmed Sessions Alert */} + {passedConfirmedSessions.length > 0 && ( + + +

+ + Sesi Terlewat ({passedConfirmedSessions.length}) +

+

+ Sesi berikut telah berakhir namun masih berstatus "Dikonfirmasi". Silakan update statusnya. +

+
+ {passedConfirmedSessions.map((session) => ( +
+ + {format(parseISO(session.session_date), 'd MMM yyyy', { locale: id })} • {session.start_time.substring(0, 5)} - {session.end_time.substring(0, 5)} • {session.profiles?.name || 'N/A'} ({session.topic_category}) + +
+ + +
+
+ ))} +
+
+
+ )} + {/* Today's Sessions Alert */} {todaySessions.length > 0 && ( @@ -451,10 +502,10 @@ export default function AdminConsulting() {

Perlu Link Meet

- + -
{pastSessions.filter(s => s.status === 'completed').length}
-

Selesai

+
{passedConfirmedSessions.length}
+

Perlu Update

diff --git a/src/pages/member/OrderDetail.tsx b/src/pages/member/OrderDetail.tsx index 3000c74..cada77c 100644 --- a/src/pages/member/OrderDetail.tsx +++ b/src/pages/member/OrderDetail.tsx @@ -75,6 +75,13 @@ export default function OrderDetail() { (item: OrderItem) => item.products.type === "consulting" ) || false; + // Check if consulting session has passed + const isConsultingSessionPassed = consultingSlots.length > 0 ? (() => { + const slot = consultingSlots[0]; + const sessionEndDateTime = new Date(`${slot.session_date}T${slot.end_time}`); + return new Date() > sessionEndDateTime; + })() : false; + // Memoized fetchOrder to avoid recreating on every render const fetchOrder = useCallback(async () => { if (!user || !id) return; @@ -758,12 +765,21 @@ export default function OrderDetail() { {/* Status Alert */} {order.payment_status === "paid" ? ( - - + isConsultingSessionPassed ? ( + + + + Sesi konsultasi telah berakhir. Menunggu konfirmasi admin. + + + ) : ( + + + ) ) : order.status !== "cancelled" && order.payment_status === "pending" && !isQrExpired ? (