Files
meet-hub/MIGRATION_GUIDE.md
dwindown 5ab4e6b974 Add calendar event lifecycle management and "Add to Calendar" feature
- Migrate consulting_slots to consulting_sessions structure
- Add calendar_event_id to track Google Calendar events
- Create delete-calendar-event edge function for auto-cleanup
- Add "Tambah ke Kalender" button for members (OrderDetail, ConsultingHistory)
- Update create-google-meet-event to store calendar event ID
- Update handle-order-paid to use consulting_sessions table
- Remove deprecated create-meet-link function
- Add comprehensive documentation (CALENDAR_INTEGRATION.md, MIGRATION_GUIDE.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-28 13:54:16 +07:00

8.5 KiB
Raw Blame History

Consulting Slots Migration - Code Updates Summary

Completed Files

1. src/pages/ConsultingBooking.tsx

  • Updated interface: ConfirmedSlotConfirmedSession with session_date field
  • Updated fetchConfirmedSlots() to query consulting_sessions table
  • Updated slot creation logic to:
    • Create ONE consulting_sessions row with session-level data
    • Create MULTIPLE consulting_time_slots rows for each 45-min block
  • Conflict checking logic already compatible (uses start_time/end_time fields)

2. supabase/functions/create-meet-link/index.ts

  • Changed update query from consulting_slots to consulting_sessions
  • Updates meet_link once per session instead of once per slot

In Progress

3. src/pages/admin/AdminConsulting.tsx (PARTIAL)

Updated:

  • Interface: ConsultingSlotConsultingSession
  • State: slotssessions, selectedSlotselectedSession
  • fetchSessions() - now queries consulting_sessions with profiles join
  • openMeetDialog() - uses session parameter
  • saveMeetLink() - updates consulting_sessions table
  • createMeetLink() - uses session fields (session_date, etc.)
  • updateSessionStatus() - renamed from updateSlotStatus()
  • Filtering logic - simplified (no grouping needed)
  • Stats sections - use sessions arrays
  • Today's Sessions Alert - uses todaySessions array

Still Needs Manual Update: Replace all remaining references in the table rendering sections (lines ~428-end):

// FIND AND REPLACE THESE PATTERNS:

// 1. Tabs list:
<TabsTrigger value="upcoming">Mendatang ({upcomingOrders.length})</TabsTrigger>
<TabsTrigger value="past">Riwayat ({pastOrders.length})</TabsTrigger>
// CHANGE TO:
<TabsTrigger value="upcoming">Mendatang ({upcomingSessions.length})</TabsTrigger>
<TabsTrigger value="past">Riwayat ({pastSessions.length})</TabsTrigger>

// 2. Desktop table - upcoming:
{upcomingOrders.map((order) => {
  const firstSlot = order.slots[0];
  const lastSlot = order.slots[order.slots.length - 1];
  const sessionCount = order.slots.length;
  return (
    <TableRow key={order.orderId || 'no-order'}>
// CHANGE TO:
{upcomingSessions.map((session) => {
  return (
    <TableRow key={session.id}>

// 3. Date cell:
{format(parseISO(firstSlot.date), 'd MMM yyyy', { locale: id })}
{isToday(parseISO(firstSlot.date)) && <Badge className="ml-2 bg-primary">Hari Ini</Badge>}
{isTomorrow(parseISO(firstSlot.date)) && <Badge className="ml-2 bg-accent">Besok</Badge>}
// CHANGE TO:
{format(parseISO(session.session_date), 'd MMM yyyy', { locale: id })}
{isToday(parseISO(session.session_date)) && <Badge className="ml-2 bg-primary">Hari Ini</Badge>}
{isTomorrow(parseISO(session.session_date)) && <Badge className="ml-2 bg-accent">Besok</Badge>}

// 4. Time cell:
<div>{firstSlot.start_time.substring(0, 5)} - {lastSlot.end_time.substring(0, 5)}</div>
{sessionCount > 1 && (
  <div className="text-xs text-muted-foreground">{sessionCount} sesi</div>
)}
// CHANGE TO:
<div>{session.start_time.substring(0, 5)} - {session.end_time.substring(0, 5)}</div>
{session.total_blocks > 1 && (
  <div className="text-xs text-muted-foreground">{session.total_blocks} blok</div>
)}

// 5. Client cell:
<p className="font-medium">{order.profile?.name || '-'}</p>
<p className="text-sm text-muted-foreground">{order.profile?.email}</p>
// CHANGE TO:
<p className="font-medium">{session.profiles?.name || '-'}</p>
<p className="text-sm text-muted-foreground">{session.profiles?.email}</p>

// 6. Category cell:
<Badge variant="outline">{firstSlot.topic_category}</Badge>
// CHANGE TO:
<Badge variant="outline">{session.topic_category}</Badge>

// 7. Status cell:
<Badge variant={statusLabels[firstSlot.status]?.variant || 'secondary'}>
  {statusLabels[firstSlot.status]?.label || firstSlot.status}
</Badge>
// CHANGE TO:
<Badge variant={statusLabels[session.status]?.variant || 'secondary'}>
  {statusLabels[session.status]?.label || session.status}
</Badge>

// 8. Meet link cell:
{order.meetLink ? (
  <a href={order.meetLink} ...>
// CHANGE TO:
{session.meet_link ? (
  <a href={session.meet_link} ...>

// 9. Action buttons:
onClick={() => openMeetDialog(firstSlot)}
onClick={() => updateSlotStatus(firstSlot.id, 'completed')}
onClick={() => updateSlotStatus(firstSlot.id, 'cancelled')}
// CHANGE TO:
onClick={() => openMeetDialog(session)}
onClick={() => updateSessionStatus(session.id, 'completed')}
onClick={() => updateSessionStatus(session.id, 'cancelled')}

// 10. Empty state:
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
  Tidak ada jadwal mendatang
</TableCell>
// CHANGE TO (same colSpan):
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
  Tidak ada jadwal mendatang
</TableCell>

// 11. Mobile card layout - same pattern as desktop:
{upcomingOrders.map((order) => {
  const firstSlot = order.slots[0];
// CHANGE TO:
{upcomingSessions.map((session) => {

// Then replace all:
// order.orderId → session.id
// order.slots[0] / firstSlot → session
// order.slots[order.slots.length - 1] / lastSlot → session
// order.profile → session.profiles
// order.meetLink → session.meet_link
// sessionCount → session.total_blocks

// 12. Past sessions tab - same pattern:
{pastOrders.slice(0, 20).map((order) => {
// CHANGE TO:
{pastSessions.slice(0, 20).map((session) => {

// 13. Dialog - selectedSlot references:
{selectedSlot && (
  <div className="p-3 bg-muted rounded-lg text-sm space-y-1">
    <p><strong>Tanggal:</strong> {format(parseISO(selectedSlot.date), 'd MMMM yyyy', { locale: id })}</p>
    <p><strong>Waktu:</strong> {selectedSlot.start_time.substring(0, 5)} - {selectedSlot.end_time.substring(0, 5)}</p>
    <p><strong>Klien:</strong> {selectedSlot.profiles?.name}</p>
    <p><strong>Topik:</strong> {selectedSlot.topic_category}</p>
    {selectedSlot.notes && <p><strong>Catatan:</strong> {selectedSlot.notes}</p>}
  </div>
)}
// CHANGE TO:
{selectedSession && (
  <div className="p-3 bg-muted rounded-lg text-sm space-y-1">
    <p><strong>Tanggal:</strong> {format(parseISO(selectedSession.session_date), 'd MMMM yyyy', { locale: id })}</p>
    <p><strong>Waktu:</strong> {selectedSession.start_time.substring(0, 5)} - {selectedSession.end_time.substring(0, 5)}</p>
    <p><strong>Klien:</strong> {selectedSession.profiles?.name}</p>
    <p><strong>Topik:</strong> {selectedSession.topic_category}</p>
    {selectedSession.notes && <p><strong>Catatan:</strong> {selectedSession.notes}</p>}
  </div>
)}

📋 Remaining Files to Update

4. src/components/reviews/ConsultingHistory.tsx

Changes needed:

  • Change query from consulting_slots to consulting_sessions
  • Remove grouping logic (no longer needed)
  • Update interface to use ConsultingSession with fields:
    • session_date (instead of date)
    • total_duration_minutes
    • total_blocks
    • total_price
  • Update all field references in rendering

5. src/pages/member/OrderDetail.tsx

Changes needed:

  • Find consulting_slots query and change to consulting_sessions
  • Update join to include session data
  • Update field names in rendering (date → session_date, etc.)

6. supabase/functions/handle-order-paid/index.ts

Changes needed:

  • Change status update from consulting_slots to consulting_sessions
  • Update logic to set status = 'confirmed' for session

Quick Reference: Field Name Changes

Old (consulting_slots) New (consulting_sessions)
date session_date
slots array Single session object
slots[0] / firstSlot session
slots[length-1] / lastSlot session
order_id (for grouping) id (session ID)
meet_link (per slot) meet_link (per session)
Row count × 45min total_duration_minutes
Row count total_blocks

Testing Checklist

After migration:

  • Test booking flow - creates session + time slots
  • Test availability checking - uses sessions table
  • Test meet link creation - updates session
  • Test admin consulting page - displays sessions
  • Test user consulting history - displays sessions
  • Test order detail - shows consulting session info
  • Test payment confirmation - updates session status

Rollback Plan (if needed)

If issues arise:

  1. Restore old table: ALTER TABLE consulting_slots RENAME TO consulting_slots_backup;
  2. Create view: CREATE VIEW consulting_slots AS SELECT ... FROM consulting_sessions JOIN consulting_time_slots;
  3. Revert code changes from git

Note: All SQL tables should already be created. This document covers code changes only.