import axiosInstance from '../../src/axiosSecure'; import { useEffect, useState, useCallback, use } from "react"; import toast from "react-hot-toast"; import { useRouter } from "next/router"; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; import bg from 'date-fns/locale/bg'; import { bgBG } from '../x-date-pickers/locales/bgBG'; import { ToastContainer } from 'react-toastify'; const common = require('src/helpers/common'); //todo import Availability type from prisma schema import { isBefore, addMinutes, isAfter, isEqual, set, getHours, getMinutes, getSeconds } from 'date-fns'; const fetchConfig = async () => { const config = await import('../../config.json'); return config.default; }; export default function AvailabilityForm({ publisherId, existingItems, inline, onDone, date }) { const router = useRouter(); const urls = { apiUrl: "/api/data/availabilities/", indexUrl: "/cart/availabilities" }; //coalsce existingItems to empty array existingItems = existingItems || []; const [editMode, setEditMode] = useState(existingItems.length > 0); const [publisher, setPublisher] = useState({ id: publisherId }); const [day, setDay] = useState(new Date(date)); const [canUpdate, setCanUpdate] = useState(true); const [timeSlots, setTimeSlots] = useState([]); const [availabilities, setAvailabilities] = useState(existingItems && existingItems.length > 0 ? existingItems : [{ publisherId: publisher.id, name: "Нов", dayofweek: "Monday", dayOfMonth: null, // startTime: "08:00", // endTime: "20:00", isActive: true, repeatWeekly: false, endDate: null, isFirst: false, isLast: false, }]); const [doRepeat, setDoRepeat] = useState(existingItems && existingItems.length > 0 ? existingItems[0].repeatWeekly : false); const [repeatFrequency, setRepeatFrequency] = useState(1); const [repeatUntil, setRepeatUntil] = useState(null); const [isInline, setInline] = useState(inline || false); const [config, setConfig] = useState(null); useEffect(() => { fetchConfig().then(config => { console.log("UI config: ", config); setConfig(config); }); }, []); // Define the minimum and maximum times const minTime = new Date(); minTime.setHours(9, 0, 0, 0); // 8:00 AM const maxTime = new Date(); maxTime.setHours(19, 30, 0, 0); // 8:00 PM useEffect(() => { setTimeSlots(generateTimeSlots(minTime, maxTime, 90, availabilities)); }, []); const fetchItemFromDB = async () => { const id = parseInt(router.query.id); if (existingItems.length == 0 && id) { try { const response = await axiosInstance.get(`/api/data/availabilities/${id}`); setAvailabilities([response.data]); setEditMode(true); setDoRepeat(response.data.repeatWeekly); } catch (error) { console.error(error); toast.error("Error fetching availability data."); } } }; useEffect(() => { fetchItemFromDB(); }, [router.query.id]); const handleSubmit = async (e) => { e.preventDefault(); try { const groupedTimeSlots = mergeCheckedTimeSlots(timeSlots); let avs = availabilities.filter(av => av.type !== "assignment"); // Determine if we need to delete and recreate, or just update let shouldRecreate = avs.length > 0 && avs.length !== groupedTimeSlots.length || avs.some(av => !av.id); shouldRecreate = shouldRecreate || (avs.length == 0 && availabilities.length > 0); //create availability if we open a form with assignment without availability if (shouldRecreate) { // Delete existing availabilities if they have an ID console.log("Recreating availabilities"); await Promise.all(avs.filter(av => av.id).map(av => axiosInstance.delete(`${urls.apiUrl}${av.id}`))); // Create new availabilities avs = await Promise.all(groupedTimeSlots.map(async group => { const newAvailability = createAvailabilityFromGroup(group, publisher.id); const response = await axiosInstance.post(urls.apiUrl, newAvailability); return response.data; // Assuming the new availability is returned })); setAvailabilities(avs); } else { // Update existing availabilities console.log("Updating existing availabilities"); avs = await Promise.all(avs.map(async (availability, index) => { const group = groupedTimeSlots[index]; const id = availability.id; const updatedAvailability = updateAvailabilityFromGroup(availability, group); delete updatedAvailability.id; //delete updatedAvailability.type; delete updatedAvailability.publisherId; delete updatedAvailability.title; delete updatedAvailability.date; updatedAvailability.publisher = { connect: { id: publisher.id } }; await axiosInstance.put(`${urls.apiUrl}${id}`, updatedAvailability); return updatedAvailability; })); setAvailabilities(avs); } handleCompletion({ updated: true }); } catch (error) { alert("Нещо се обърка. Моля, опитайте отново по-късно."); // toast.error("Нещо се обърка. Моля, опитайте отново по-късно."); // console.error(error.message); // try { // const { data: session, status } = useSession(); // const userId = session.user.id; // axiosInstance.post('/log', { message: error.message, userId: userId }); // } // catch (err) { // console.error("Error logging error: ", err); // } } }; function mergeCheckedTimeSlots(timeSlots) { const selectedSlots = timeSlots.filter(slot => slot.isChecked); // Sort the selected intervals by start time const sortedSlots = [...selectedSlots].sort((a, b) => a.startTime - b.startTime); // Group continuous slots const groupedIntervals = []; let currentGroup = [sortedSlots[0]]; for (let i = 1; i < sortedSlots.length; i++) { const previousSlot = currentGroup[currentGroup.length - 1]; const currentSlot = sortedSlots[i]; // Calculate the difference in hours between slots const difference = (currentSlot.startTime - previousSlot.endTime) / (60 * 60 * 1000); // Assuming each slot represents an exact match to the increment (1.5 hours), we group them if (difference === 0) { currentGroup.push(currentSlot); } else { groupedIntervals.push(currentGroup); currentGroup = [currentSlot]; } } // Don't forget the last group if (currentGroup.length > 0) { groupedIntervals.push(currentGroup); } return groupedIntervals; } // Common function to set shared properties function setSharedAvailabilityProperties(availability, group, timeSlots) { let startTime = new Date(availability.startTime || day); startTime.setHours(group[0].startTime.getHours(), group[0].startTime.getMinutes(), group[0].startTime.getSeconds(), 0); let endTime = new Date(availability.endTime || day); endTime.setHours(group[group.length - 1].endTime.getHours(), group[group.length - 1].endTime.getMinutes(), group[group.length - 1].endTime.getSeconds(), 0); availability.startTime = startTime; availability.endTime = endTime; availability.name = common.getTimeFomatted(startTime) + "-" + common.getTimeFomatted(endTime); availability.isWithTransportIn = group[0].isFirst && timeSlots[0].isWithTransport; availability.isWithTransportOut = group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport; // Adjustments for repeating settings if (doRepeat) { availability.repeatWeekly = true; availability.type = "Weekly" availability.dayOfMonth = null; availability.endDate = repeatUntil; } else { availability.type = "OneTime" availability.repeatWeekly = false; availability.dayOfMonth = startTime.getDate(); availability.endDate = null; } availability.dateOfEntry = new Date(); } function createAvailabilityFromGroup(group) { let availability = { publisherId: publisher.id, dayofweek: common.getDayOfWeekNameEnEnumForDate(day), }; setSharedAvailabilityProperties(availability, group, timeSlots); return availability; } function updateAvailabilityFromGroup(availability, group) { setSharedAvailabilityProperties(availability, group, timeSlots); delete availability.weekOfMonth; if (doRepeat) { availability.weekOfMonth = 0; } if (availability.parentAvailabilityId) { availability.parentAvailability = { connect: { id: parentAvailabilityId } }; } delete availability.parentAvailabilityId; return availability; } const handleDelete = async (e) => { e.preventDefault(); try { let avs = availabilities.filter(av => av.type !== "assignment"); const deletePromises = avs.map(async (availability) => { if (availability.id) { // console.log("deleting publisher id = ", router.query.id, "; url=" + urls.apiUrl + router.query.id); await axiosInstance.delete(urls.apiUrl + availability.id); } }); await Promise.all(deletePromises); toast.success("Записът изтрит", { position: "bottom-center", }); if (handleCompletion) { handleCompletion({ deleted: true }); } } catch (error) { //alert("Нещо се обърка при изтриването. Моля, опитайте отново или се свържете с нас"); console.log(JSON.stringify(error)); toast.error(error.response?.data?.message || "An error occurred"); fetchItemFromDB(); } }; const handleCompletion = async (result) => { console.log("AvailabilityForm: handleCompletion"); if (isInline) { if (onDone) { onDone(result); } } else { router.push(urls.indexUrl); } } // console.log("AvailabilityForm: publisherId: " + publisher.id + ", id: " + availabilit .id, ", inline: " + isInline); //ToDo: this is examplary function to be used in the future. replace all date/time related functions with this one const generateTimeSlots = (start, end, increment, items) => { const slots = []; let currentTime = start; const baseDate = new Date(2000, 0, 1); // Use a constant date for all time comparisons while (isBefore(currentTime, end)) { let slotStart = normalizeTime(currentTime, baseDate); let slotEnd = normalizeTime(addMinutes(currentTime, increment), baseDate); const isChecked = items.some(item => { let itemStart = item.startTime ? normalizeTime(new Date(item.startTime), baseDate) : null; let itemEnd = item.endTime ? normalizeTime(new Date(item.endTime), baseDate) : null; return itemStart && itemEnd && (slotStart.getTime() < itemEnd.getTime()) && (slotEnd.getTime() >= itemStart.getTime()); }); slots.push({ startTime: slotStart, endTime: slotEnd, isChecked: isChecked, }); currentTime = addMinutes(currentTime, increment); } if (slots.length > 0 && items?.length > 0) { slots[0].isFirst = true; slots[slots.length - 1].isLast = true; slots[0].isWithTransport = items[0]?.isWithTransportIn; slots[slots.length - 1].isWithTransport = items[items.length - 1]?.isWithTransportOut; } return slots; }; // Normalize the time part of a date by using a base date function normalizeTime(date, baseDate) { return set(baseDate, { hours: getHours(date), minutes: getMinutes(date), seconds: getSeconds(date), milliseconds: 0 }); } const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => { const [allDay, setAllDay] = useState(slots.every(slot => slot.isChecked)); const handleAllDayChange = (e) => { const updatedSlots = slots.map(slot => ({ ...slot, isChecked: e.target.checked, })); setSlots(updatedSlots); setAllDay(e.target.checked) // setCanUpdate(slots.some(slot => slot.isChecked)); const anyChecked = updatedSlots.some(slot => slot.isChecked); setCanUpdate(anyChecked); console.log("handleAllDayChange: allDay: " + allDay + ", updatedSlots: " + JSON.stringify(updatedSlots)); }; useEffect(() => { console.log("allDay updated to: ", allDay); const updatedSlots = slots.map(slot => ({ ...slot, isChecked: allDay })); //setSlots(updatedSlots); }, [allDay]); const handleSlotCheckedChange = (changedSlot) => { const updatedSlots = slots.map(slot => { if (slot.startTime === changedSlot.startTime && slot.endTime === changedSlot.endTime) { return { ...slot, isChecked: !slot.isChecked }; } return slot; }); // If slot is either first or last and it's being unchecked, also uncheck and disable transport if ((changedSlot.isFirst || changedSlot.isLast) && !changedSlot.isChecked) { changedSlot.isWithTransport = false; } //if no slots are checked, disable Update button const anyChecked = updatedSlots.some(slot => slot.isChecked); setCanUpdate(anyChecked); setSlots(updatedSlots); }; const handleTransportChange = (changedSlot) => { const updatedSlots = slots.map(slot => { if (slot.startTime === changedSlot.startTime && slot.endTime === changedSlot.endTime) { return { ...slot, isWithTransport: !slot.isWithTransport }; } return slot; }); setSlots(updatedSlots); }; return ( <> {slots.map((slot, index) => { const slotLabel = `${common.getTimeFomatted(slot.startTime)} до ${common.getTimeFomatted(slot.endTime)}`; slot.transportNeeded = slot.isFirst || slot.isLast; // Determine if the current slot is the first or the last return (
{/* Conditionally render transport checkbox based on slot being first or last */} {slot.transportNeeded && ( )}
); })} ); }; return (

{editMode ? "Редактирай" : "Нова"} възможност

setDay({ value })} />
{false && repeatWeekly && (
setRepeatUntil({ value })} />
)}
{/* Time slot checkboxes */}
{editMode && ( <> )}
); }