UNPKG

@unified-api/react-calendar-scheduler

Version:
155 lines (151 loc) 11.3 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); const CalendarScheduler = ({ busy = [], available = [], connection_id, api_token, duration = 60, months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], type = 'Appointment', bookFn, defaultTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone, work_days = [1, 2, 3, 4, 5], // Monday to Friday dc = 'us', start_hour = 9, end_hour = 17, }) => { const [selectedDate, setSelectedDate] = react.useState(new Date()); const [selectedTime, setSelectedTime] = react.useState(''); const [currentMonth, setCurrentMonth] = react.useState(new Date()); const [showBookingForm, setShowBookingForm] = react.useState(false); const [busySlots, setBusySlots] = react.useState(busy); const [availableSlots, setAvailableSlots] = react.useState(available); const [loading, setLoading] = react.useState(false); // Fetch busy/available slots from API if connection_id is provided react.useEffect(() => { const fetchCalendarData = async () => { if (connection_id && api_token) { setLoading(true); try { const baseUrl = dc === 'eu' ? 'https://eu.unified.to' : dc === 'au' ? 'https://au.unified.to' : 'https://unified.to'; const response = await fetch(`${baseUrl}/calendar/busy`, { method: 'POST', headers: { Authorization: `Bearer ${api_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ connection_id, start_at: new Date().toISOString(), end_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), }), }); const data = await response.json(); setBusySlots(data.busy || []); } catch (error) { console.error('Error fetching calendar data:', error); } finally { setLoading(false); } } }; fetchCalendarData(); }, [connection_id, api_token, dc]); // Generate calendar days for current month const calendarDays = react.useMemo(() => { const year = currentMonth.getFullYear(); const month = currentMonth.getMonth(); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const startDate = new Date(firstDay); // Start from the beginning of the week startDate.setDate(startDate.getDate() - startDate.getDay()); const days = []; const endDate = new Date(lastDay); endDate.setDate(endDate.getDate() + (6 - endDate.getDay())); for (let date = new Date(startDate); date <= endDate; date.setDate(date.getDate() + 1)) { days.push(new Date(date)); } return days; }, [currentMonth]); // Generate available time slots for selected date const timeSlots = react.useMemo(() => { if (!selectedDate) return []; const dayOfWeek = selectedDate.getDay(); if (!work_days.includes(dayOfWeek)) return []; const slots = []; const dateStr = selectedDate.toISOString().split('T')[0]; for (let hour = start_hour; hour < end_hour; hour++) { for (let minute = 0; minute < 60; minute += duration) { const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; const slotStart = new Date(`${dateStr}T${timeStr}:00`); const slotEnd = new Date(slotStart.getTime() + duration * 60 * 1000); // Check if slot conflicts with busy times const isConflict = busySlots.some((busy) => { const busyStart = new Date(busy.start_at); const busyEnd = new Date(busy.end_at); return slotStart < busyEnd && slotEnd > busyStart; }); // Check if slot is in available times (if specified) const isAvailable = availableSlots.length === 0 || availableSlots.some((avail) => { const availStart = new Date(avail.start_at); const availEnd = new Date(avail.end_at); return slotStart >= availStart && slotEnd <= availEnd; }); if (!isConflict && isAvailable) { slots.push(timeStr); } } } return slots; }, [selectedDate, busySlots, availableSlots, work_days, start_hour, end_hour, duration]); const handleDateSelect = (date) => { setSelectedDate(date); setSelectedTime(''); }; const handleTimeSelect = (time) => { setSelectedTime(time); setShowBookingForm(true); }; const handleBooking = (formData) => { if (bookFn) { const bookingData = { ...formData, date: selectedDate.toISOString().split('T')[0], time: selectedTime, duration, timezone: defaultTimezone, }; bookFn(bookingData); } setShowBookingForm(false); setSelectedTime(''); }; const isDateAvailable = (date) => { const dayOfWeek = date.getDay(); return work_days.includes(dayOfWeek); }; const isCurrentMonth = (date) => { return date.getMonth() === currentMonth.getMonth(); }; return (jsxRuntime.jsxs("div", { className: "calendar-scheduler", children: [jsxRuntime.jsxs("div", { className: "calendar-scheduler-grid", children: [jsxRuntime.jsxs("div", { className: "calendar-section", children: [jsxRuntime.jsxs("div", { className: "calendar-header", children: [jsxRuntime.jsx("button", { onClick: () => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1)), children: "\u2190" }), jsxRuntime.jsxs("h2", { children: [months[currentMonth.getMonth()], " ", currentMonth.getFullYear()] }), jsxRuntime.jsx("button", { onClick: () => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1)), children: "\u2192" })] }), jsxRuntime.jsxs("div", { className: "calendar-grid", children: [jsxRuntime.jsx("div", { className: "calendar-days-header", children: days.map((day) => (jsxRuntime.jsx("div", { children: day.slice(0, 3) }, day))) }), jsxRuntime.jsx("div", { className: "calendar-days-grid", children: calendarDays.map((date, index) => { const isAvailable = isDateAvailable(date); const isCurrent = isCurrentMonth(date); const isSelected = selectedDate && date.toDateString() === selectedDate.toDateString(); return (jsxRuntime.jsx("button", { onClick: () => isAvailable && isCurrent && handleDateSelect(date), disabled: !isAvailable || !isCurrent, className: `calendar-day-button ${isSelected ? 'selected' : ''} ${!isCurrent ? 'other-month' : ''}`, children: date.getDate() }, index)); }) })] })] }), jsxRuntime.jsxs("div", { className: "time-slots-section", children: [jsxRuntime.jsx("h3", { children: selectedDate ? `Available times for ${selectedDate.toLocaleDateString()}` : 'Select a date' }), loading && (jsxRuntime.jsxs("div", { className: "loading-container", children: [jsxRuntime.jsx("div", { className: "loading-spinner" }), jsxRuntime.jsx("p", { className: "loading-text", children: "Loading available times..." })] })), selectedDate && !loading && (jsxRuntime.jsx("div", { className: "time-slots-grid", children: timeSlots.length > 0 ? (timeSlots.map((time) => (jsxRuntime.jsx("button", { onClick: () => handleTimeSelect(time), className: "time-slot-button", children: time }, time)))) : (jsxRuntime.jsx("p", { className: "no-times-message", children: "No available times for this date" })) }))] })] }), showBookingForm && (jsxRuntime.jsx("div", { className: "modal-overlay", children: jsxRuntime.jsxs("div", { className: "modal-content", children: [jsxRuntime.jsxs("h3", { className: "modal-title", children: ["Book ", type] }), jsxRuntime.jsx(BookingForm, { selectedDate: selectedDate, selectedTime: selectedTime, duration: duration, onSubmit: handleBooking, onCancel: () => setShowBookingForm(false) })] }) }))] })); }; const BookingForm = ({ selectedDate, selectedTime, duration, onSubmit, onCancel }) => { const [formData, setFormData] = react.useState({ name: '', email: '', notes: '', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, }); const handleSubmit = () => { onSubmit(formData); }; const handleChange = (e) => { setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value, })); }; return (jsxRuntime.jsxs("div", { className: "booking-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { className: "form-label", children: "Date & Time" }), jsxRuntime.jsxs("p", { className: "form-text", children: [selectedDate.toLocaleDateString(), " at ", selectedTime, " (", duration, " minutes)"] })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { className: "form-label", children: "Name *" }), jsxRuntime.jsx("input", { type: "text", name: "name", required: true, value: formData.name, onChange: handleChange, className: "form-input" })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { className: "form-label", children: "Email *" }), jsxRuntime.jsx("input", { type: "email", name: "email", required: true, value: formData.email, onChange: handleChange, className: "form-input" })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { className: "form-label", children: "Timezone" }), jsxRuntime.jsx("input", { type: "text", name: "timezone", value: formData.timezone, onChange: handleChange, className: "form-input" })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { className: "form-label", children: "Notes" }), jsxRuntime.jsx("textarea", { name: "notes", rows: 3, value: formData.notes, onChange: handleChange, className: "form-textarea" })] }), jsxRuntime.jsxs("div", { className: "form-buttons", children: [jsxRuntime.jsx("button", { onClick: handleSubmit, disabled: !formData.name || !formData.email, className: "btn btn-primary", children: "Book Appointment" }), jsxRuntime.jsx("button", { type: "button", onClick: onCancel, className: "btn btn-secondary", children: "Cancel" })] })] })); }; exports.CalendarScheduler = CalendarScheduler; //# sourceMappingURL=index.js.map