# Calendar Event Management - Complete Implementation ## Summary ✅ **Google Calendar integration is now fully bidirectional:** - ✅ Creates events when sessions are booked - ✅ Stores Google Calendar event ID for tracking - ✅ Deletes events when sessions are cancelled - ✅ Members can add events to their own calendar with one click --- ## What Was Fixed ### 1. ✅ `create-google-meet-event` Updated to Use `consulting_sessions` **File**: `supabase/functions/create-google-meet-event/index.ts` **Changes:** - Removed old `consulting_slots` queries (lines 317-334, 355-373) - Now updates `consulting_sessions` table instead - Stores both `meet_link` AND `calendar_event_id` in the session - Much simpler - just update one row per session **Before:** ```typescript // Had to check order_id and update multiple slots const { data: slotData } = await supabase .from("consulting_slots") .select("order_id") .eq("id", body.slot_id) .single(); if (slotData?.order_id) { await supabase .from("consulting_slots") .update({ meet_link: meetLink }) .eq("order_id", slotData.order_id); } ``` **After:** ```typescript // Just update the session directly await supabase .from("consulting_sessions") .update({ meet_link: meetLink, calendar_event_id: eventDataResult.id // ← NEW: Store event ID! }) .eq("id", body.slot_id); ``` --- ### 2. ✅ Database Migration - Add `calendar_event_id` Column **File**: `supabase/migrations/20241228_add_calendar_event_id.sql` ```sql -- Add column to store Google Calendar event ID ALTER TABLE consulting_sessions ADD COLUMN calendar_event_id TEXT; -- Index for faster lookups CREATE INDEX idx_consulting_sessions_calendar_event ON consulting_sessions(calendar_event_id); COMMENT ON COLUMN consulting_sessions.calendar_event_id IS 'Google Calendar event ID - used to delete events when sessions are cancelled/refunded'; ``` **What this does:** - Stores the Google Calendar event ID for each consulting session - Allows us to delete the event later when session is cancelled/refunded - No more orphaned calendar events! --- ### 3. ✅ New Edge Function: `delete-calendar-event` **File**: `supabase/functions/delete-calendar-event/index.ts` **What it does:** 1. Takes a `session_id` as input 2. Retrieves the session's `calendar_event_id` 3. Uses Google Calendar API to DELETE the event 4. Clears the `calendar_event_id` from the database **API Usage:** ```typescript await supabase.functions.invoke('delete-calendar-event', { body: { session_id: 'session-uuid-here' } }); ``` **Google Calendar API Call:** ```http DELETE https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events/{eventId} Authorization: Bearer {access_token} ``` **Error Handling:** - If event already deleted (410 Gone): Logs and continues - If calendar not configured: Returns success (graceful degradation) - If deletion fails: Logs error but doesn't block the operation --- ### 4. ✅ Admin Panel Integration - Auto-Delete on Cancel **File**: `src/pages/admin/AdminConsulting.tsx` **Changes:** - Added `calendar_event_id` to `ConsultingSession` interface - Updated `updateSessionStatus()` to call `delete-calendar-event` before cancelling - Calendar events are automatically deleted when admin cancels a session **Code:** ```typescript const updateSessionStatus = async (sessionId: string, newStatus: string) => { // If cancelling and session has a calendar event, delete it first if (newStatus === 'cancelled') { const session = sessions.find(s => s.id === sessionId); if (session?.calendar_event_id) { try { await supabase.functions.invoke('delete-calendar-event', { body: { session_id: sessionId } }); } catch (err) { console.log('Failed to delete calendar event:', err); // Continue with status update even if calendar deletion fails } } } // Update session status const { error } = await supabase .from('consulting_sessions') .update({ status: newStatus }) .eq('id', sessionId); if (!error) { toast({ title: 'Berhasil', description: `Status diubah ke ${statusLabels[newStatus]?.label || newStatus}` }); fetchSessions(); } }; ``` --- ### 5. ✅ "Add to Calendar" Button for Members **Files**: `src/pages/member/OrderDetail.tsx`, `src/components/reviews/ConsultingHistory.tsx` **What it does:** - Allows members to add consulting sessions to their own Google Calendar - Uses Google Calendar's public URL format (no OAuth required) - One-click addition with event details pre-filled **How it works:** ```typescript // Generate Google Calendar link const generateCalendarLink = (session: ConsultingSession) => { if (!session.meet_link) return null; const startDate = new Date(`${session.session_date}T${session.start_time}`); const endDate = new Date(`${session.session_date}T${session.end_time}`); // Format dates for Google Calendar (YYYYMMDDTHHmmssZ) const formatDate = (date: Date) => { return date.toISOString().replace(/-|:|\.\d\d\d/g, ''); }; const params = new URLSearchParams({ action: 'TEMPLATE', text: `Konsultasi: ${session.topic_category || 'Sesi Konsultasi'}`, dates: `${formatDate(startDate)}/${formatDate(endDate)}`, details: `Link Meet: ${session.meet_link}`, location: session.meet_link, }); return `https://www.google.com/calendar/render?${params.toString()}`; }; ``` **UI Implementation:** **OrderDetail.tsx** (after meet link): ```tsx {consultingSlots[0]?.meet_link && (

Google Meet Link

{consultingSlots[0].meet_link.substring(0, 40)}...
)} ``` **ConsultingHistory.tsx** (upcoming sessions): ```tsx {session.meet_link && ( <> )} ``` **Google Calendar URL Format:** ``` https://www.google.com/calendar/render?action=TEMPLATE&text=Title&dates=StartDate/EndDate&details=Description&location=Location ``` **Benefits:** - ✅ No OAuth required for users - ✅ Works with any calendar app that supports Google Calendar links - ✅ Pre-fills all event details (title, time, description, location) - ✅ Opens in user's default calendar app - ✅ One-click addition --- ## Event Flow ### Booking Flow (Create) ``` User books consulting ↓ ConsultingBooking.tsx creates session in DB ↓ handle-order-paid edge function triggered ↓ Calls create-google-meet-event ↓ Creates event in Google Calendar ↓ Returns meet_link + event_id ↓ Updates consulting_sessions: - meet_link = "https://meet.google.com/xxx-xxx" - calendar_event_id = "event_id_from_google" ``` ### Cancellation Flow (Delete) ``` Admin cancels session in AdminConsulting.tsx ↓ Calls delete-calendar-event edge function ↓ Retrieves calendar_event_id from consulting_sessions ↓ Calls Google Calendar API to DELETE event ↓ Clears calendar_event_id from database ↓ Updates session status to 'cancelled' ``` --- ## Google Calendar API Response When an event is created, Google returns: ```json { "id": "a1b2c3d4e5f6g7h8i9j0", // ← Calendar event ID "status": "confirmed", "htmlLink": "https://www.google.com/calendar/event?eid=a1b2c3d4...", "created": "2024-12-28T10:00:00.000Z", "updated": "2024-12-28T10:00:00.000Z", "summary": "Konsultasi: Career Guidance - John Doe", "description": "Client: john@example.com\n\nNotes: ...\n\nSlot ID: uuid-here", "start": { "dateTime": "2025-01-15T09:00:00+07:00", "timeZone": "Asia/Jakarta" }, "end": { "dateTime": "2025-01-15T12:00:00+07:00", "timeZone": "Asia/Jakarta" }, "conferenceData": { "entryPoints": [ { "entryPointType": "video", "uri": "https://meet.google.com/abc-defg-hij", // ← Meet link "label": "meet.google.com" } ] } } ``` **Important fields:** - `id` - Event ID (stored in `calendar_event_id`) - `conferenceData.entryPoints[0].uri` - Meet link (stored in `meet_link`) --- ## Testing Checklist ### ✅ Test Event Creation - [ ] Book a consulting session - [ ] Verify Google Calendar event is created - [ ] Verify `meet_link` is saved to `consulting_sessions` - [ ] Verify `calendar_event_id` is saved to `consulting_sessions` ### ✅ Test Event Deletion - [ ] Cancel a session in admin panel - [ ] Verify Google Calendar event is deleted - [ ] Verify `calendar_event_id` is cleared from database - [ ] Verify session status is set to 'cancelled' ### ✅ Test Edge Cases - [ ] Cancel session without calendar event (should not fail) - [ ] Cancel session when Google Calendar not configured (should not fail) - [ ] Delete already-deleted event (410 Gone - should handle gracefully) --- ## SQL Migration Steps Run this migration to add the `calendar_event_id` column: ```bash # Connect to your Supabase database psql -h db.xxx.supabase.co -U postgres -d postgres # Or use Supabase Dashboard: # SQL Editor → Paste and Run ``` ```sql -- Add calendar_event_id column ALTER TABLE consulting_sessions ADD COLUMN calendar_event_id TEXT; -- Create index CREATE INDEX idx_consulting_sessions_calendar_event ON consulting_sessions(calendar_event_id); -- Verify SELECT id, session_date, start_time, end_time, meet_link, calendar_event_id FROM consulting_sessions; ``` --- ## Deploy Edge Functions ```bash # Deploy the updated create-google-meet-event function supabase functions deploy create-google-meet-event # Deploy the new delete-calendar-event function supabase functions deploy delete-calendar-event ``` Or use the Supabase Dashboard: - Edge Functions → Select function → Deploy --- ## Future Enhancements ### Option 1: Auto-reschedule If session date/time changes: - Delete old event - Create new event with updated time - Update `calendar_event_id` in database ### Option 2: Batch Delete If multiple sessions are cancelled (e.g., order refund): - Get all `calendar_event_id`s for the order - Delete all events in batch - Clear all `calendar_event_id`s ### Option 3: Event Sync Periodic sync to ensure database and calendar are in sync: - Check all upcoming sessions - Verify events exist in Google Calendar - Recreate if missing (with warning) --- ## Troubleshooting ### Issue: Event not deleted when session cancelled **Check:** 1. Does the session have `calendar_event_id`? ```sql SELECT id, calendar_event_id FROM consulting_sessions WHERE id = 'session-uuid'; ``` 2. Are the OAuth credentials valid? ```sql SELECT google_oauth_config FROM platform_settings; ``` 3. Check the edge function logs: ```bash supabase functions logs delete-calendar-event ``` ### Issue: "Token exchange failed" **Solution:** Refresh OAuth credentials in settings - Go to: Admin → Settings → Integrations - Update `google_oauth_config` with new `refresh_token` ### Issue: Event already deleted (410 Gone) **This is normal!** The function handles this gracefully and continues. --- ## Files Modified 1. ✅ `supabase/functions/create-google-meet-event/index.ts` - Use consulting_sessions, store calendar_event_id 2. ✅ `supabase/migrations/20241228_add_calendar_event_id.sql` - Add calendar_event_id column 3. ✅ `supabase/functions/delete-calendar-event/index.ts` - NEW: Delete calendar events 4. ✅ `src/pages/admin/AdminConsulting.tsx` - Auto-delete on cancel, add calendar_event_id to interface 5. ✅ `src/pages/member/OrderDetail.tsx` - Add "Tambah ke Kalender" button 6. ✅ `src/components/reviews/ConsultingHistory.tsx` - Add "Tambah ke Kalender" button --- **All set!** 🎉 Your consulting sessions now have full calendar lifecycle management.