import React, { useState, useEffect } from 'react'; import { Calendar, momentLocalizer, dateFnsLocalizer } from 'react-big-calendar'; import 'react-big-calendar/lib/css/react-big-calendar.css'; import AvailabilityForm from '../availability/AvailabilityForm'; import common from '../../src/helpers/common'; import { toast } from 'react-toastify'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import moment from 'moment'; import 'moment/locale/bg'; // Import Bulgarian locale import { ArrowLeftCircleIcon } from '@heroicons/react/24/outline'; import { FaArrowLeft, FaArrowRight, FaRegCalendarAlt, FaRegListAlt, FaRegCalendarCheck } from 'react-icons/fa'; import { MdToday } from 'react-icons/md'; import { useSwipeable } from 'react-swipeable'; import axiosInstance from '../../src/axiosSecure'; // Set moment to use the Bulgarian locale moment.locale('bg'); const localizer = momentLocalizer(moment); // Bulgarian translations for Calendar labels const messages = { allDay: 'Цял ден', previous: 'Предишен', next: 'Следващ', today: 'Днес', month: 'Месец', week: 'Седмица', day: 'Ден', agenda: 'Дневен ред', date: 'Дата', time: 'Час', event: 'Събитие', // or 'Събитие' depending on context // Any other labels you want to translate... }; const AvCalendar = ({ publisherId, events, selectedDate }) => { const [date, setDate] = useState(new Date()); const [currentView, setCurrentView] = useState('month'); const [evts, setEvents] = useState(events); // Existing events const [displayedEvents, setDisplayedEvents] = useState(evts); // Events to display in the calendar const [isModalOpen, setIsModalOpen] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); const [visibleRange, setVisibleRange] = useState(() => { const start = new Date(); start.setDate(1); // Set to the first day of the current month const end = new Date(start.getFullYear(), start.getMonth() + 1, 0); // Last day of the current month return { start, end }; }); // Update internal state when `events` prop changes useEffect(() => { setEvents(events); // Call any function here to process and set displayedEvents // based on the new events, if necessary }, [events]); const onRangeChange = (range) => { if (Array.isArray(range)) { // For week and day views, range is an array of dates setVisibleRange({ start: range[0], end: range[range.length - 1] }); } else { // For month view, range is an object with start and end setVisibleRange(range); } }; useEffect(() => { if (currentView === 'agenda') { const filtered = evts?.filter(event => event.type === "assignment"); setDisplayedEvents(filtered); } else { // Function to generate weekly occurrences of an event const recurringEvents = evts?.filter(event => event.type !== "assignment" && (event.dayOfMonth === null || event.dayOfMonth === undefined)) || []; const occurrences = recurringEvents?.flatMap(event => generateOccurrences(event, visibleRange.start, visibleRange.end)) || []; const nonRecurringEvents = evts?.filter(event => event.dayOfMonth !== null) || []; setDisplayedEvents([...nonRecurringEvents, ...recurringEvents, ...occurrences]); } //setDisplayedEvents(evts); }, [visibleRange, evts, currentView]); const handlers = useSwipeable({ onSwipedLeft: () => navigate('NEXT'), onSwipedRight: () => navigate('PREV'), preventDefaultTouchmoveEvent: true, trackMouse: true, }); const navigate = (action) => { console.log('navigate', action); setDate((currentDate) => { const newDate = new Date(currentDate); if (action === 'NEXT') { newDate.setMonth(newDate.getMonth() + 1); } else if (action === 'PREV') { newDate.setMonth(newDate.getMonth() - 1); } return newDate; }); }; const generateOccurrences = (event, start, end) => { const occurrences = []; const eventStart = new Date(event.startTime); let current = new Date(event.startTime); // Assuming startTime has the start date // Determine the end date for the event series const seriesEndDate = event.endDate ? new Date(event.endDate) : end; seriesEndDate.setHours(23, 59, 59); // Set to the end of the day while (current <= seriesEndDate && current <= end) { // Check if the event should be repeated weekly or on a specific day of the month if (event.repeatWeekly && current.getDay() === eventStart.getDay()) { // For weekly recurring events addOccurrence(event, current, occurrences); } else if (event.dayOfMonth && current.getDate() === event.dayOfMonth) { // For specific day of month events addOccurrence(event, current, occurrences); } // Move to the next day current = new Date(current.setDate(current.getDate() + 1)); } return occurrences; }; // Helper function to add an occurrence const addOccurrence = (event, current, occurrences) => { // Skip the original event date const eventStart = new Date(event.startTime); const eventEnd = new Date(event.endTime); if (current.toDateString() !== eventStart.toDateString()) { const occurrence = { ...event, startTime: new Date(current.setHours(eventStart.getHours(), eventStart.getMinutes())), endTime: new Date(current.setHours(eventEnd.getHours(), eventEnd.getMinutes())), date: current, type: 'recurring' }; occurrences.push(occurrence); } }; // Define min and max times const minHour = 8; // 8:00 AM const maxHour = 20; // 8:00 PM const minTime = new Date(); minTime.setHours(minHour, 0, 0); const maxTime = new Date(); maxTime.setHours(maxHour, 0, 0); const totalHours = maxHour - minHour; const handleSelect = ({ start, end }) => { if (!start || !end) return; if (start < new Date() || end < new Date() || start > end) return; // Check if start and end are on the same day if (start.toDateString() !== end.toDateString()) { end = common.setTimeHHmm(start, "23:59"); } const startMinutes = common.getTimeInMinutes(start); const endMinutes = common.getTimeInMinutes(end); // Adjust start and end times to be within min and max hours if (startMinutes < common.getTimeInMinutes(common.setTimeHHmm(start, minHour))) { start = common.setTimeHHmm(start, minHour); } if (endMinutes > common.getTimeInMinutes(common.setTimeHHmm(end, maxHour))) { end = common.setTimeHHmm(end, maxHour); } setDate(start); // get exising events for the selected date const existingEvents = evts?.filter(event => event.publisherId === publisherId && event.startTime === start.toDateString()); setSelectedEvent({ date: start, startTime: start, endTime: end, dayOfMonth: start.getDate(), isactive: true, publisherId: publisherId, // Add any other initial values needed //set dayOfMonth to null, so that we repeat the availability every week dayOfMonth: null, }); setIsModalOpen(true); }; const handleEventClick = (event) => { if (event.type === "assignment") return; // Handle event click const eventForEditing = { ...event, startTime: new Date(event.startTime), endTime: new Date(event.endTime), publisherId: event.publisherId || event.publisher?.connect?.id, repeatWeekly: event.repeatWeekly || false, }; //strip title, start, end and allDay properties delete eventForEditing.title; delete eventForEditing.start; delete eventForEditing.end; delete eventForEditing.type; delete eventForEditing.publisher console.log("handleEventClick: " + eventForEditing); setSelectedEvent(eventForEditing); setIsModalOpen(true); }; const handleDialogClose = async (dialogEvent) => { setIsModalOpen(false); if (dialogEvent === null || dialogEvent === undefined) { } else { // if (dialogEvent.deleted) { // // Remove the old event from the calendar // setEvents(currentEvents => currentEvents.filter(e => e.id !== selectedEvent.id)); // } // else { // // Update the event data // dialogEvent.start = dialogEvent.startTime; // dialogEvent.end = dialogEvent.endTime; // // Update the events array by first removing the old event and then adding the updated one // setEvents(currentEvents => { // const filteredEvents = currentEvents?.filter(e => e.id !== selectedEvent.id) || []; // return [...filteredEvents, dialogEvent]; // }); // } //refresh the events from the server let events = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisherId}`); var newEvents = events.data; setEvents(newEvents); } console.log("handleSave: ", dialogEvent); }; const handleCancel = () => { setIsModalOpen(false); }; const EventWrapper = ({ event, style }) => { const [isHovered, setIsHovered] = useState(false); let eventStyle = { ...style }; const handleMouseEnter = () => setIsHovered(true); const handleMouseLeave = () => setIsHovered(false); if (currentView !== 'agenda') { //if event.type is availability show in blue. if it is schedule - green if confirmed, yellow if not confirmed //if event is not active - show in gray let bgColorClass = 'bg-gray-500'; // Default color for inactive events var bgColor = event.isactive ? "" : "bg-gray-500"; if (event.type === "assignment") { bgColor = event.isConfirmed ? "bg-green-500" : "bg-yellow-500"; //event.title = event.publisher.name; //ToDo: add other publishers names //event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime); } else { if (event.start !== undefined && event.end !== undefined && event.startTime !== null && event.endTime !== null) { try { if (event.type === "recurring") { //bgColor = "bg-blue-300"; event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime); } else { event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime); } } catch (err) { event.title = event.startTime + " - " + event.endTime; console.log("Error in EventWrapper: " + err); } } } eventStyle = { ...style, // backgroundColor: bgColorClass, //height: "50px", //color: 'white', whiteSpace: 'normal', // Allow the text to wrap to the next line overflow: 'hidden', // Hide overflowed content textOverflow: 'ellipsis' // Add ellipsis to text that's too long to fit }; } const onDelete = (event) => { // Remove the event from the calendar setEvents(currentEvents => currentEvents.filter(e => e.id !== event.id)); }; const onConfirm = (event) => { console.log("onConfirm: " + event.id); toast.info("Вие потвърдихте!", { autoClose: 2000 }); // Update the event data event.isConfirmed = true; event.isactive = false; // Update the events array by first removing the old event and then adding the updated one setEvents(currentEvents => { const filteredEvents = currentEvents.filter(e => e.id !== event.id); return [...filteredEvents, event]; }); }; return (