- 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>
8.5 KiB
8.5 KiB
Consulting Slots Migration - Code Updates Summary
✅ Completed Files
1. src/pages/ConsultingBooking.tsx ✅
- Updated interface:
ConfirmedSlot→ConfirmedSessionwithsession_datefield - Updated
fetchConfirmedSlots()to queryconsulting_sessionstable - Updated slot creation logic to:
- Create ONE
consulting_sessionsrow with session-level data - Create MULTIPLE
consulting_time_slotsrows for each 45-min block
- Create ONE
- Conflict checking logic already compatible (uses
start_time/end_timefields)
2. supabase/functions/create-meet-link/index.ts ✅
- Changed update query from
consulting_slotstoconsulting_sessions - Updates meet_link once per session instead of once per slot
⏳ In Progress
3. src/pages/admin/AdminConsulting.tsx (PARTIAL)
Updated:
- Interface:
ConsultingSlot→ConsultingSession - State:
slots→sessions,selectedSlot→selectedSession fetchSessions()- now queriesconsulting_sessionswith profiles joinopenMeetDialog()- uses session parametersaveMeetLink()- updatesconsulting_sessionstablecreateMeetLink()- uses session fields (session_date, etc.)updateSessionStatus()- renamed fromupdateSlotStatus()- Filtering logic - simplified (no grouping needed)
- Stats sections - use
sessionsarrays - Today's Sessions Alert - uses
todaySessionsarray
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_slotstoconsulting_sessions - Remove grouping logic (no longer needed)
- Update interface to use
ConsultingSessionwith fields:session_date(instead ofdate)total_duration_minutestotal_blockstotal_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_slotstoconsulting_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:
- Restore old table:
ALTER TABLE consulting_slots RENAME TO consulting_slots_backup; - Create view:
CREATE VIEW consulting_slots AS SELECT ... FROM consulting_sessions JOIN consulting_time_slots; - Revert code changes from git
Note: All SQL tables should already be created. This document covers code changes only.