From 08538c87c3dfd0457565fdc22b0339f37dddb75e Mon Sep 17 00:00:00 2001 From: salirezav Date: Mon, 22 Sep 2025 11:34:58 -0400 Subject: [PATCH] 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. --- .../src/components/Scheduling.tsx | 100 ++++++++++-------- .../src/lib/supabase.ts | 65 ++++++++++++ 2 files changed, 121 insertions(+), 44 deletions(-) diff --git a/management-dashboard-web-app/src/components/Scheduling.tsx b/management-dashboard-web-app/src/components/Scheduling.tsx index 210a3b2..fddbe3b 100644 --- a/management-dashboard-web-app/src/components/Scheduling.tsx +++ b/management-dashboard-web-app/src/components/Scheduling.tsx @@ -1,8 +1,9 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' // @ts-ignore - react-big-calendar types not available import { Calendar, momentLocalizer, Views } from 'react-big-calendar' import moment from 'moment' import type { User } from '../lib/supabase' +import { availabilityManagement } from '../lib/supabase' import 'react-big-calendar/lib/css/react-big-calendar.css' import './CalendarStyles.css' @@ -331,36 +332,7 @@ function ScheduleExperiment({ user, onBack }: { user: User; onBack: () => void } function AvailabilityCalendar({ user }: { user: User }) { // User context available for future features like saving preferences const localizer = momentLocalizer(moment) - const [events, setEvents] = useState([ - { - 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 [events, setEvents] = useState([]) const [selectedDate, setSelectedDate] = useState(null) const [showTimeSlotForm, setShowTimeSlotForm] = useState(false) @@ -370,6 +342,26 @@ function AvailabilityCalendar({ user }: { user: User }) { }) 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) => { return { style: { @@ -397,13 +389,22 @@ function AvailabilityCalendar({ user }: { user: User }) { }) } - const handleSelectEvent = (event: CalendarEvent) => { - if (window.confirm('Do you want to remove this availability?')) { + const handleSelectEvent = async (event: CalendarEvent) => { + 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)) + } 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 const [startHour, startMinute] = newTimeSlot.startTime.split(':').map(Number) @@ -432,17 +433,28 @@ function AvailabilityCalendar({ user }: { user: User }) { return } - const newEvent = { - id: Date.now(), - title: 'Available', - start: startDateTime, - end: endDateTime, - resource: 'available' - } + try { + // Persist to DB first to get real ID and server validation + const created = await availabilityManagement.createAvailability({ + available_from: startDateTime.toISOString(), + available_to: endDateTime.toISOString() + }) - setEvents([...events, newEvent]) - setShowTimeSlotForm(false) - setSelectedDate(null) + const newEvent: CalendarEvent = { + id: created.id, + 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 = () => { diff --git a/management-dashboard-web-app/src/lib/supabase.ts b/management-dashboard-web-app/src/lib/supabase.ts index e8071fe..19a1da9 100755 --- a/management-dashboard-web-app/src/lib/supabase.ts +++ b/management-dashboard-web-app/src/lib/supabase.ts @@ -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 { + 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 { + 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 { + const { error } = await supabase + .from('conductor_availability') + .update({ status: 'cancelled' }) + .eq('id', id) + + if (error) throw error + } +}