diff --git a/src/pages/ConsultingBooking.tsx b/src/pages/ConsultingBooking.tsx index 4929ac4..6f6d510 100644 --- a/src/pages/ConsultingBooking.tsx +++ b/src/pages/ConsultingBooking.tsx @@ -82,6 +82,31 @@ export default function ConsultingBooking() { useEffect(() => { fetchData(); + + // Check for pre-filled data from expired order + const expiredOrderData = sessionStorage.getItem('expiredConsultingOrder'); + if (expiredOrderData) { + try { + const data = JSON.parse(expiredOrderData); + if (data.fromExpiredOrder) { + // Prefill form with expired order data + if (data.topicCategory) setSelectedCategory(data.topicCategory); + if (data.notes) setNotes(data.notes); + + // Show notification to user + setTimeout(() => { + // You could add a toast notification here if you have toast set up + console.log('Pre-filled data from expired order:', data); + }, 100); + + // Clear the stored data after using it + sessionStorage.removeItem('expiredConsultingOrder'); + } + } catch (err) { + console.error('Error parsing expired order data:', err); + sessionStorage.removeItem('expiredConsultingOrder'); + } + } }, []); useEffect(() => { diff --git a/src/pages/member/OrderDetail.tsx b/src/pages/member/OrderDetail.tsx index 71cd94b..91dea74 100644 --- a/src/pages/member/OrderDetail.tsx +++ b/src/pages/member/OrderDetail.tsx @@ -449,15 +449,32 @@ export default function OrderDetail() { {isConsultingOrder ? ( - // Consulting order - show booking button + // Consulting order - show booking button with pre-filled data

Order ini telah dibatalkan secara otomatis karena waktu pembayaran habis.

- +

+ Kategori dan catatan akan terisi otomatis dari order sebelumnya +

) : ( // Product order - show regenerate button diff --git a/supabase/functions/cancel-expired-consulting-orders/index.ts b/supabase/functions/cancel-expired-consulting-orders/index.ts new file mode 100644 index 0000000..eba264f --- /dev/null +++ b/supabase/functions/cancel-expired-consulting-orders/index.ts @@ -0,0 +1,136 @@ +import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; +import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", +}; + +serve(async (req: Request): Promise => { + if (req.method === "OPTIONS") { + return new Response(null, { headers: corsHeaders }); + } + + try { + const supabaseUrl = Deno.env.get("SUPABASE_URL")!; + const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; + const supabase = createClient(supabaseUrl, supabaseServiceKey); + + console.log("[CANCEL-EXPIRED] Starting check for expired consulting orders"); + + // Find expired pending consulting orders + const now = new Date().toISOString(); + + // Get orders with consulting_sessions that are pending payment and QR is expired + const { data: expiredOrders, error: queryError } = await supabase + .from("orders") + .select(` + id, + payment_status, + qr_expires_at, + consulting_sessions ( + id, + topic_category, + notes, + session_date, + start_time, + end_time + ) + `) + .eq("payment_status", "pending") + .lt("qr_expires_at", now) + .not("consulting_sessions", "is", null); + + if (queryError) { + console.error("[CANCEL-EXPIRED] Query error:", queryError); + throw queryError; + } + + if (!expiredOrders || expiredOrders.length === 0) { + console.log("[CANCEL-EXPIRED] No expired orders found"); + return new Response( + JSON.stringify({ + success: true, + message: "No expired orders to process", + processed: 0 + }), + { headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } + + console.log(`[CANCEL-EXPIRED] Found ${expiredOrders.length} expired orders`); + + let processedCount = 0; + + // Process each expired order + for (const order of expiredOrders) { + console.log(`[CANCEL-EXPIRED] Processing order: ${order.id}`); + + // Update order status to cancelled + const { error: updateError } = await supabase + .from("orders") + .update({ status: "cancelled" }) + .eq("id", order.id); + + if (updateError) { + console.error(`[CANCEL-EXPIRED] Failed to update order ${order.id}:`, updateError); + continue; + } + + // Cancel all consulting sessions for this order + if (order.consulting_sessions && order.consulting_sessions.length > 0) { + for (const session of order.consulting_sessions) { + // Delete calendar event if exists + if (session.calendar_event_id) { + try { + await supabase.functions.invoke('delete-calendar-event', { + body: { session_id: session.id } + }); + console.log(`[CANCEL-EXPIRED] Deleted calendar event for session: ${session.id}`); + } catch (err) { + console.log(`[CANCEL-EXPIRED] Failed to delete calendar event: ${err}`); + // Continue anyway + } + } + + // Update session status to cancelled + await supabase + .from("consulting_sessions") + .update({ status: "cancelled" }) + .eq("id", session.id); + + // Delete or release time slots + await supabase + .from("consulting_time_slots") + .delete() + .eq("session_id", session.id); + + console.log(`[CANCEL-EXPIRED] Cancelled session: ${session.id}`); + } + } + + processedCount++; + } + + console.log(`[CANCEL-EXPIRED] Successfully processed ${processedCount} orders`); + + return new Response( + JSON.stringify({ + success: true, + message: `Successfully cancelled ${processedCount} expired consulting orders`, + processed: processedCount + }), + { headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + + } catch (error: any) { + console.error("[CANCEL-EXPIRED] Error:", error); + return new Response( + JSON.stringify({ + success: false, + error: error.message || "Internal server error" + }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } +});