diff --git a/src/pages/admin/AdminOrders.tsx b/src/pages/admin/AdminOrders.tsx index aaa00bf..a70a68e 100644 --- a/src/pages/admin/AdminOrders.tsx +++ b/src/pages/admin/AdminOrders.tsx @@ -10,7 +10,7 @@ import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Skeleton } from "@/components/ui/skeleton"; import { formatIDR, formatDateTime } from "@/lib/format"; -import { Eye, CheckCircle, XCircle } from "lucide-react"; +import { Eye, CheckCircle, XCircle, Video, ExternalLink } from "lucide-react"; import { toast } from "@/hooks/use-toast"; interface Order { @@ -27,11 +27,20 @@ interface Order { interface OrderItem { id: string; - product: { title: string }; + product: { title: string; type?: string }; unit_price: number; quantity: number; } +interface ConsultingSlot { + id: string; + date: string; + start_time: string; + end_time: string; + status: string; + meet_link?: string; +} + export default function AdminOrders() { const { user, isAdmin, loading: authLoading } = useAuth(); const navigate = useNavigate(); @@ -39,6 +48,7 @@ export default function AdminOrders() { const [loading, setLoading] = useState(true); const [selectedOrder, setSelectedOrder] = useState(null); const [orderItems, setOrderItems] = useState([]); + const [consultingSlots, setConsultingSlots] = useState([]); const [dialogOpen, setDialogOpen] = useState(false); useEffect(() => { @@ -60,8 +70,22 @@ export default function AdminOrders() { const viewOrderDetails = async (order: Order) => { setSelectedOrder(order); - const { data } = await supabase.from("order_items").select("*, product:products(title)").eq("order_id", order.id); - setOrderItems((data as unknown as OrderItem[]) || []); + const { data: itemsData } = await supabase.from("order_items").select("*, product:products(title, type)").eq("order_id", order.id); + setOrderItems((itemsData as unknown as OrderItem[]) || []); + + // Check if any item is a consulting product and fetch slots + const hasConsulting = (itemsData as unknown as OrderItem[])?.some(item => item.product?.type === "consulting"); + if (hasConsulting) { + const { data: slotsData } = await supabase + .from("consulting_slots") + .select("*") + .eq("order_id", order.id) + .order("date", { ascending: true }); + setConsultingSlots((slotsData as ConsultingSlot[]) || []); + } else { + setConsultingSlots([]); + } + setDialogOpen(true); }; @@ -199,6 +223,56 @@ export default function AdminOrders() { {formatIDR(selectedOrder.total_amount)} + + {/* Consulting Slots */} + {consultingSlots.length > 0 && ( +
+

+

+
+ {consultingSlots.map((slot) => ( +
+
+
+
+ + {slot.status === "confirmed" ? "Terkonfirmasi" : slot.status} + +
+

+ {new Date(slot.date).toLocaleDateString("id-ID", { + weekday: "short", + day: "numeric", + month: "short", + year: "numeric" + })} +

+

+ {slot.start_time.substring(0, 5)} - {slot.end_time.substring(0, 5)} WIB +

+
+ {slot.meet_link && ( + + )} +
+
+ ))} +
+
+ )} +
{selectedOrder.payment_status !== "paid" && ( + )} + {slot.meet_link && order.payment_status !== "paid" && ( +

+ Link tersedia setelah pembayaran +

+ )} + {!slot.meet_link && ( +

+ Link akan dikirim via email +

+ )} +
+ + ))} + + + + )} + {/* Access Info */} {order.payment_status === "paid" && ( diff --git a/supabase/functions/create-google-meet-event/index.ts b/supabase/functions/create-google-meet-event/index.ts index a9370e0..ebc12a9 100644 --- a/supabase/functions/create-google-meet-event/index.ts +++ b/supabase/functions/create-google-meet-event/index.ts @@ -229,18 +229,31 @@ serve(async (req: Request): Promise => { console.log("Creating event in calendar:", calendarId); console.log("Event data:", JSON.stringify(eventData, null, 2)); - // Create event via Google Calendar API - const calendarResponse = await fetch( - `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events?conferenceDataVersion=1`, - { - method: "POST", - headers: { - "Authorization": `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(eventData), - } - ); + // Create event via Google Calendar API with better error handling + let calendarResponse: Response; + try { + calendarResponse = await fetch( + `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events?conferenceDataVersion=1`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": "application/json", + "User-Agent": "Deno/1.0 (Supabase Edge Function)", + }, + body: JSON.stringify(eventData), + } + ); + } catch (fetchError: any) { + console.error("Network error calling Google Calendar API:", fetchError); + return new Response( + JSON.stringify({ + success: false, + message: "Network error calling Google Calendar API: " + fetchError.message + }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } + ); + } console.log("Calendar API response status:", calendarResponse.status);