import { useEffect, useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { supabase } from '@/integrations/supabase/client'; import { useAuth } from '@/hooks/useAuth'; import { AppLayout } from '@/components/AppLayout'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Calendar } from '@/components/ui/calendar'; import { Skeleton } from '@/components/ui/skeleton'; import { toast } from '@/hooks/use-toast'; import { formatIDR } from '@/lib/format'; import { Video, Clock, Calendar as CalendarIcon, MessageSquare } from 'lucide-react'; import { format, addMinutes, parse, isAfter, isBefore, startOfDay, addDays, isSameDay } from 'date-fns'; import { id } from 'date-fns/locale'; interface ConsultingSettings { id: string; is_consulting_enabled: boolean; consulting_block_price: number; consulting_block_duration_minutes: number; consulting_categories: string; } interface Workhour { id: string; weekday: number; start_time: string; end_time: string; } interface ConfirmedSlot { date: string; start_time: string; end_time: string; } interface TimeSlot { start: string; end: string; available: boolean; } interface Profile { whatsapp_number: string | null; } export default function ConsultingBooking() { const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); const [settings, setSettings] = useState(null); const [workhours, setWorkhours] = useState([]); const [confirmedSlots, setConfirmedSlots] = useState([]); const [loading, setLoading] = useState(true); const [profile, setProfile] = useState(null); const [selectedDate, setSelectedDate] = useState(addDays(new Date(), 1)); const [selectedSlots, setSelectedSlots] = useState([]); const [selectedCategory, setSelectedCategory] = useState(''); const [notes, setNotes] = useState(''); const [whatsappInput, setWhatsappInput] = useState(''); const [submitting, setSubmitting] = useState(false); useEffect(() => { fetchData(); }, []); useEffect(() => { if (selectedDate) { fetchConfirmedSlots(selectedDate); } }, [selectedDate]); const fetchData = async () => { const [settingsRes, workhoursRes, profileRes] = await Promise.all([ supabase.from('consulting_settings').select('*').single(), supabase.from('workhours').select('*').order('weekday'), user ? supabase.from('profiles').select('whatsapp_number').eq('id', user.id).single() : Promise.resolve({ data: null }), ]); if (settingsRes.data) setSettings(settingsRes.data); if (workhoursRes.data) setWorkhours(workhoursRes.data); if (profileRes.data) setProfile(profileRes.data); setLoading(false); }; const fetchConfirmedSlots = async (date: Date) => { const dateStr = format(date, 'yyyy-MM-dd'); const { data } = await supabase .from('consulting_slots') .select('date, start_time, end_time') .eq('date', dateStr) .in('status', ['pending_payment', 'confirmed']); if (data) setConfirmedSlots(data); }; const categories = useMemo(() => { if (!settings?.consulting_categories) return []; return settings.consulting_categories.split(',').map(c => c.trim()).filter(Boolean); }, [settings?.consulting_categories]); const availableSlots = useMemo((): TimeSlot[] => { if (!selectedDate || !settings) return []; const dayOfWeek = selectedDate.getDay(); const dayWorkhours = workhours.filter(w => w.weekday === dayOfWeek); if (dayWorkhours.length === 0) return []; const slots: TimeSlot[] = []; const duration = settings.consulting_block_duration_minutes; const now = new Date(); const isToday = isSameDay(selectedDate, now); for (const wh of dayWorkhours) { let current = parse(wh.start_time, 'HH:mm:ss', selectedDate); const end = parse(wh.end_time, 'HH:mm:ss', selectedDate); while (isBefore(addMinutes(current, duration), end) || format(addMinutes(current, duration), 'HH:mm') === format(end, 'HH:mm')) { const slotStart = format(current, 'HH:mm'); const slotEnd = format(addMinutes(current, duration), 'HH:mm'); // Check if slot conflicts with confirmed/pending slots const isConflict = confirmedSlots.some(cs => { const csStart = cs.start_time.substring(0, 5); const csEnd = cs.end_time.substring(0, 5); return !(slotEnd <= csStart || slotStart >= csEnd); }); // Check if slot is in the past for today const isPassed = isToday && isBefore(current, now); slots.push({ start: slotStart, end: slotEnd, available: !isConflict && !isPassed, }); current = addMinutes(current, duration); } } return slots; }, [selectedDate, workhours, confirmedSlots, settings]); const toggleSlot = (slotStart: string) => { setSelectedSlots(prev => prev.includes(slotStart) ? prev.filter(s => s !== slotStart) : [...prev, slotStart] ); }; const totalBlocks = selectedSlots.length; const totalPrice = totalBlocks * (settings?.consulting_block_price || 0); const totalDuration = totalBlocks * (settings?.consulting_block_duration_minutes || 30); const handleBookNow = async () => { if (!user) { toast({ title: 'Login diperlukan', description: 'Silakan login untuk melanjutkan', variant: 'destructive' }); navigate('/auth'); return; } if (selectedSlots.length === 0) { toast({ title: 'Pilih slot', description: 'Pilih minimal satu slot waktu', variant: 'destructive' }); return; } if (!selectedCategory) { toast({ title: 'Pilih kategori', description: 'Pilih kategori konsultasi', variant: 'destructive' }); return; } if (!selectedDate || !settings) return; setSubmitting(true); try { // Save WhatsApp number if provided and not already saved if (whatsappInput && !profile?.whatsapp_number) { let normalized = whatsappInput.replace(/\D/g, ''); if (normalized.startsWith('0')) normalized = '62' + normalized.substring(1); if (!normalized.startsWith('+')) normalized = '+' + normalized; await supabase.from('profiles').update({ whatsapp_number: normalized }).eq('id', user.id); } // Create order const { data: order, error: orderError } = await supabase .from('orders') .insert({ user_id: user.id, total_amount: totalPrice, status: 'pending', payment_status: 'pending', payment_provider: 'pakasir', }) .select() .single(); if (orderError) throw orderError; // Create consulting slots const slotsToInsert = selectedSlots.map(slotStart => { const slotEnd = format( addMinutes(parse(slotStart, 'HH:mm', new Date()), settings.consulting_block_duration_minutes), 'HH:mm' ); return { user_id: user.id, order_id: order.id, date: format(selectedDate, 'yyyy-MM-dd'), start_time: slotStart + ':00', end_time: slotEnd + ':00', status: 'pending_payment', topic_category: selectedCategory, notes: notes, }; }); const { error: slotsError } = await supabase.from('consulting_slots').insert(slotsToInsert); if (slotsError) throw slotsError; // Call edge function to create Pakasir payment (avoids CORS) const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-pakasir-payment', { body: { order_id: order.id, amount: totalPrice, description: `Konsultasi 1-on-1 (${totalBlocks} blok)`, }, }); if (paymentError) { console.error('Payment creation error:', paymentError); throw new Error(paymentError.message || 'Gagal membuat pembayaran'); } if (paymentData?.success && paymentData?.data?.payment_url) { // Redirect to Pakasir payment page window.location.href = paymentData.data.payment_url; } else { throw new Error('Gagal membuat URL pembayaran'); } } catch (error: any) { toast({ title: 'Error', description: error.message, variant: 'destructive' }); } finally { setSubmitting(false); } }; if (loading || authLoading) { return (
); } if (!settings?.is_consulting_enabled) { return (

Layanan Konsultasi Tidak Tersedia

Layanan konsultasi sedang tidak aktif.

); } return (

Pilih waktu dan kategori untuk sesi konsultasi pribadi

{/* Calendar & Slots */}
Pilih Tanggal date < startOfDay(new Date()) || date.getDay() === 0} locale={id} className="rounded-md border-2" /> {selectedDate && ( Slot Waktu - {format(selectedDate, 'EEEE, d MMMM yyyy', { locale: id })} Klik slot untuk memilih. {settings.consulting_block_duration_minutes} menit per blok. {availableSlots.length === 0 ? (

Tidak ada slot tersedia pada hari ini

) : (
{availableSlots.map((slot) => ( ))}
)}
)} Kategori Konsultasi
{categories.map((cat) => ( ))}
Catatan (Opsional)