Implement availability management in Scheduling component
- Added functionality to load user availability from the database on component mount. - Integrated create and delete availability features with the backend. - Refactored event handling to manage availability slots dynamically. - Updated supabase.ts to include methods for fetching, creating, and deleting availability records.
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
// @ts-ignore - react-big-calendar types not available
|
// @ts-ignore - react-big-calendar types not available
|
||||||
import { Calendar, momentLocalizer, Views } from 'react-big-calendar'
|
import { Calendar, momentLocalizer, Views } from 'react-big-calendar'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import type { User } from '../lib/supabase'
|
import type { User } from '../lib/supabase'
|
||||||
|
import { availabilityManagement } from '../lib/supabase'
|
||||||
import 'react-big-calendar/lib/css/react-big-calendar.css'
|
import 'react-big-calendar/lib/css/react-big-calendar.css'
|
||||||
import './CalendarStyles.css'
|
import './CalendarStyles.css'
|
||||||
|
|
||||||
@@ -331,36 +332,7 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void }
|
|||||||
function AvailabilityCalendar({ user }: { user: User }) {
|
function AvailabilityCalendar({ user }: { user: User }) {
|
||||||
// User context available for future features like saving preferences
|
// User context available for future features like saving preferences
|
||||||
const localizer = momentLocalizer(moment)
|
const localizer = momentLocalizer(moment)
|
||||||
const [events, setEvents] = useState<CalendarEvent[]>([
|
const [events, setEvents] = useState<CalendarEvent[]>([])
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Available - Morning',
|
|
||||||
start: new Date(2024, 11, 15, 9, 0), // December 15, 2024, 9:00 AM
|
|
||||||
end: new Date(2024, 11, 15, 12, 0), // December 15, 2024, 12:00 PM
|
|
||||||
resource: 'available'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'Available - Afternoon',
|
|
||||||
start: new Date(2024, 11, 15, 14, 0), // December 15, 2024, 2:00 PM
|
|
||||||
end: new Date(2024, 11, 15, 17, 0), // December 15, 2024, 5:00 PM
|
|
||||||
resource: 'available'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: 'Available - Full Day',
|
|
||||||
start: new Date(2024, 11, 16, 9, 0), // December 16, 2024, 9:00 AM
|
|
||||||
end: new Date(2024, 11, 16, 17, 0), // December 16, 2024, 5:00 PM
|
|
||||||
resource: 'available'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: 'Unavailable - Personal',
|
|
||||||
start: new Date(2024, 11, 20, 0, 0), // December 20, 2024
|
|
||||||
end: new Date(2024, 11, 20, 23, 59), // December 20, 2024
|
|
||||||
resource: 'unavailable'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const [selectedDate, setSelectedDate] = useState<Date | null>(null)
|
const [selectedDate, setSelectedDate] = useState<Date | null>(null)
|
||||||
const [showTimeSlotForm, setShowTimeSlotForm] = useState(false)
|
const [showTimeSlotForm, setShowTimeSlotForm] = useState(false)
|
||||||
@@ -370,6 +342,26 @@ function AvailabilityCalendar({ user }: { user: User }) {
|
|||||||
})
|
})
|
||||||
const [currentView, setCurrentView] = useState(Views.MONTH)
|
const [currentView, setCurrentView] = useState(Views.MONTH)
|
||||||
|
|
||||||
|
// Load availability from DB on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const loadAvailability = async () => {
|
||||||
|
try {
|
||||||
|
const records = await availabilityManagement.getMyAvailability()
|
||||||
|
const mapped: CalendarEvent[] = records.map(r => ({
|
||||||
|
id: r.id,
|
||||||
|
title: 'Available',
|
||||||
|
start: new Date(r.available_from),
|
||||||
|
end: new Date(r.available_to),
|
||||||
|
resource: 'available'
|
||||||
|
}))
|
||||||
|
setEvents(mapped)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load availability', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadAvailability()
|
||||||
|
}, [])
|
||||||
|
|
||||||
const eventStyleGetter = (event: CalendarEvent) => {
|
const eventStyleGetter = (event: CalendarEvent) => {
|
||||||
return {
|
return {
|
||||||
style: {
|
style: {
|
||||||
@@ -397,13 +389,22 @@ function AvailabilityCalendar({ user }: { user: User }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSelectEvent = (event: CalendarEvent) => {
|
const handleSelectEvent = async (event: CalendarEvent) => {
|
||||||
if (window.confirm('Do you want to remove this availability?')) {
|
if (!window.confirm('Do you want to remove this availability?')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof event.id === 'string') {
|
||||||
|
await availabilityManagement.deleteAvailability(event.id)
|
||||||
|
}
|
||||||
setEvents(events.filter(e => e.id !== event.id))
|
setEvents(events.filter(e => e.id !== event.id))
|
||||||
|
} catch (e: any) {
|
||||||
|
alert(e?.message || 'Failed to delete availability.')
|
||||||
|
console.error('Failed to delete availability', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddTimeSlot = () => {
|
const handleAddTimeSlot = async () => {
|
||||||
if (!selectedDate) return
|
if (!selectedDate) return
|
||||||
|
|
||||||
const [startHour, startMinute] = newTimeSlot.startTime.split(':').map(Number)
|
const [startHour, startMinute] = newTimeSlot.startTime.split(':').map(Number)
|
||||||
@@ -432,17 +433,28 @@ function AvailabilityCalendar({ user }: { user: User }) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const newEvent = {
|
try {
|
||||||
id: Date.now(),
|
// Persist to DB first to get real ID and server validation
|
||||||
title: 'Available',
|
const created = await availabilityManagement.createAvailability({
|
||||||
start: startDateTime,
|
available_from: startDateTime.toISOString(),
|
||||||
end: endDateTime,
|
available_to: endDateTime.toISOString()
|
||||||
resource: 'available'
|
})
|
||||||
}
|
|
||||||
|
|
||||||
setEvents([...events, newEvent])
|
const newEvent: CalendarEvent = {
|
||||||
setShowTimeSlotForm(false)
|
id: created.id,
|
||||||
setSelectedDate(null)
|
title: 'Available',
|
||||||
|
start: new Date(created.available_from),
|
||||||
|
end: new Date(created.available_to),
|
||||||
|
resource: 'available'
|
||||||
|
}
|
||||||
|
|
||||||
|
setEvents([...events, newEvent])
|
||||||
|
setShowTimeSlotForm(false)
|
||||||
|
setSelectedDate(null)
|
||||||
|
} catch (e: any) {
|
||||||
|
alert(e?.message || 'Failed to save availability. Please try again.')
|
||||||
|
console.error('Failed to create availability', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancelTimeSlot = () => {
|
const handleCancelTimeSlot = () => {
|
||||||
|
|||||||
@@ -976,3 +976,68 @@ export const phaseDraftManagement = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conductor Availability Management
|
||||||
|
export interface ConductorAvailability {
|
||||||
|
id: string
|
||||||
|
user_id: string
|
||||||
|
available_from: string
|
||||||
|
available_to: string
|
||||||
|
notes?: string | null
|
||||||
|
status: 'active' | 'cancelled'
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
created_by: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAvailabilityRequest {
|
||||||
|
available_from: string
|
||||||
|
available_to: string
|
||||||
|
notes?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const availabilityManagement = {
|
||||||
|
async getMyAvailability(): Promise<ConductorAvailability[]> {
|
||||||
|
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||||
|
if (authError || !user) throw new Error('User not authenticated')
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('conductor_availability')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', user.id)
|
||||||
|
.eq('status', 'active')
|
||||||
|
.order('available_from', { ascending: true })
|
||||||
|
|
||||||
|
if (error) throw error
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
|
||||||
|
async createAvailability(request: CreateAvailabilityRequest): Promise<ConductorAvailability> {
|
||||||
|
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||||
|
if (authError || !user) throw new Error('User not authenticated')
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('conductor_availability')
|
||||||
|
.insert({
|
||||||
|
user_id: user.id,
|
||||||
|
available_from: request.available_from,
|
||||||
|
available_to: request.available_to,
|
||||||
|
notes: request.notes,
|
||||||
|
created_by: user.id
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single()
|
||||||
|
|
||||||
|
if (error) throw error
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteAvailability(id: string): Promise<void> {
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('conductor_availability')
|
||||||
|
.update({ status: 'cancelled' })
|
||||||
|
.eq('id', id)
|
||||||
|
|
||||||
|
if (error) throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user