import axiosInstance from '../../src/axiosSecure'; import { useEffect, useState } from "react"; import toast from "react-hot-toast"; import { useRouter } from "next/router"; import DayOfWeek from "../DayOfWeek"; const common = require('src/helpers/common'); import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'; 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 { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import TextField from '@mui/material/TextField'; import bg from 'date-fns/locale/bg'; // Bulgarian locale import { bgBG } from '../x-date-pickers/locales/bgBG'; // Your custom translation file import { ToastContainer } from 'react-toastify'; const fetchConfig = async () => { const config = await import('../../config.json'); return config.default; }; /* // ------------------ data model ------------------ model Availability { id Int @id @default(autoincrement()) publisher Publisher @relation(fields: [publisherId], references: [id], onDelete: Cascade) publisherId String name String dayofweek DayOfWeek dayOfMonth Int? weekOfMonth Int? startTime DateTime endTime DateTime isactive Boolean @default(true) type AvailabilityType @default(Weekly) isWithTransport Boolean @default(false) isFromPreviousAssignment Boolean @default(false) isFromPreviousMonth Boolean @default(false) repeatWeekly Boolean? // New field to indicate weekly repetition repeatFrequency Int? // New field to indicate repetition frequency endDate DateTime? // New field for the end date of repetition @@map("Availability") } */ //enum for abailability type - day of week or day of month; and array of values const AvailabilityType = { WeeklyRecurrance: 'WeeklyRecurrance', ExactDate: 'ExactDate' } //const AvailabilityTypeValues = Object.values(AvailabilityType); export default function AvailabilityForm({ publisherId, existingItem, inline, onDone, itemsForDay }) { const [availability, setAvailability] = useState(existingItem || { publisherId: publisherId || null, name: "Нов", dayofweek: "Monday", dayOfMonth: null, startTime: "08:00", endTime: "20:00", isactive: true, repeatWeekly: false, endDate: null, }); const [items, setItems] = useState(itemsForDay || []); // [existingItem, ...items] const [selectedType, setSelectedOption] = useState(AvailabilityType.WeeklyRecurrance); const [isInline, setInline] = useState(inline || false); const [timeSlots, setTimeSlots] = useState([]); const [isMobile, setIsMobile] = useState(false); // Check screen width to determine if the device is mobile useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth < 768); // 768px is a common breakpoint for mobile devices }; // Call the function to setAvailability the initial state handleResize(); // Add event listener window.addEventListener('resize', handleResize); // Cleanup return () => window.removeEventListener('resize', handleResize); }, []); // Inside your component const [config, setConfig] = useState(null); useEffect(() => { fetchConfig().then(config => { // Use config here to adjust form fields console.log("UI config: ", config); setConfig(config); }); }, []); const [dataFetched, setDataFetched] = useState(false); const router = useRouter(); const initialId = existingItem?.id || router.query.id; const urls = { apiUrl: "/api/data/availabilities/", indexUrl: "/cart/availabilities" }; // Define the minimum and maximum times const minTime = new Date(); minTime.setHours(8, 0, 0, 0); // 8:00 AM const maxTime = new Date(); maxTime.setHours(20, 0, 0, 0); // 8:00 PM //always setAvailability publisherId useEffect(() => { availability.publisherId = publisherId; console.log("availability.publisherId: ", availability.publisherId); }, [availability]); if (typeof window !== 'undefined') { useEffect(() => { // If component is not in inline mode and there's no existing availability, fetch the availability based on the query ID // Fetch availability from DB only if it's not fetched yet, and there's no existing availability if (!isInline && !existingItem && !dataFetched && router.query.id) { fetchItemFromDB(parseInt(router.query.id.toString())); setDataFetched(true); // Set data as fetched } }, [router.query.id, isInline, existingItem, dataFetched]); } // const [isEdit, setIsEdit] = useState(false); const fetchItemFromDB = async (id) => { try { console.log("fetching availability " + id); const { data } = await axiosInstance.get(urls.apiUrl + id); data.startTime = formatTime(data.startTime); data.endTime = formatTime(data.endTime); setAvailability(data); console.log(data); } catch (error) { console.error(error); } }; const handleChange = ({ target }) => { // const { name, value } = e.target; // setItem((prev) => ({ ...prev, [name]: value })); console.log("AvailabilityForm: handleChange: " + target.name + " = " + target.value); setAvailability({ ...availability, [target.name]: target.value }); } const handleSubmit = async (e) => { e.preventDefault(); try { if (!availability.name) { // availability.name = "От календара"; availability.name = common.getTimeFomatted(availability.startTime) + "-" + common.getTimeFomatted(availability.endTime); } availability.dayofweek = common.getDayOfWeekNameEnEnum(availability.startTime); if (availability.repeatWeekly) { availability.dayOfMonth = null; } else { availability.endDate = null; availability.dayOfMonth = availability.startTime.getDate(); } delete availability.date; //remove date from availability as it is not part of the db model // ---------------------- CB UI -------------- if (config.checkboxUI.enabled) { 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); } // Create availability objects from grouped slots const availabilities = groupedIntervals.map(group => { const startTime = group[0].startTime; const endTime = group[group.length - 1].endTime; return { publisherId: availability.publisherId, startTime: startTime, endTime: endTime, // isWithTransportIn: slots[0].isWithTransport, // Add other necessary fields, like isWithTransport if applicable }; }); //if more than one interval, we delete and recreate the availability, as it is not possble to map them if (availability.id && availabilities.length > 1) { await axiosInstance.delete(urls.apiUrl + availability.id); delete availability.id; } // const firstSlotWithTransport = timeSlots[0].checked && timeSlots[0]?.isWithTransport; // const lastSlotWithTransport = timeSlots[timeSlots.length - 1].checked && timeSlots[timeSlots.length - 1]?.isWithTransport; availabilities.forEach(async av => { // expand availability const avToStore = { ...availability, ...av, startTime: av.startTime, endTime: av.endTime, name: "От календара", id: undefined, // isWithTransportIn: firstSlotWithTransport, // isWithTransportOut: lastSlotWithTransport, }; console.log("AvailabilityForm: handleSubmit: " + av); if (availability.id) { // UPDATE EXISTING ITEM await axiosInstance.put(urls.apiUrl + availability.id, { ...avToStore, }); } else { // CREATE NEW ITEM await axiosInstance.post(urls.apiUrl, avToStore); } handleCompletion(avToStore); // Assuming `handleCompletion` is defined to handle post-save logic }); } // ---------------------- TimePicker UI -------------- else { availability.publisher = { connect: { id: availability.publisherId } }; delete availability.publisherId; if (availability.id) { console.log('editing avail# ' + availability.id); //delete availability.id; // UPDATE EXISTING ITEM var itemUpdate = { ...availability, id: undefined }; await axiosInstance.put(urls.apiUrl + availability.id, { ...itemUpdate, }); toast.success("Task Updated", { position: "bottom-center", }); } else { // CREATE NEW ITEM console.log('creating new avail: ' + availability); const response = await axiosInstance.post(urls.apiUrl, availability); const createdItem = response.data; availability.id = createdItem.id; toast.success("Task Saved", { position: "bottom-center", }); } } handleCompletion(availability); } catch (error) { alert("Нещо се обърка. Моля, опитайте отново по-късно."); toast.error("Нещо се обърка. Моля, опитайте отново по-късно."); console.error(error.message); } }; const handleDelete = async (e) => { e.preventDefault(); try { if (availability.id) { // console.log("deleting publisher id = ", router.query.id, "; url=" + urls.apiUrl + router.query.id); await axiosInstance.delete(urls.apiUrl + availability.id); toast.success("Записът изтрит", { position: "bottom-center", }); handleCompletion({ deleted: true }); // Assuming handleCompletion is defined and properly handles post-deletion logic } } catch (error) { alert("Нещо се обърка при изтриването. Моля, опитайте отново или се свържете с нас"); console.log(JSON.stringify(error)); toast.error(error.response?.data?.message || "An error occurred"); } }; const handleCompletion = async (result) => { console.log("AvailabilityForm: handleCompletion"); if (isInline) { if (onDone) { onDone(result); } } else { router.push(urls.indexUrl); } } console.log("AvailabilityForm: publisherId: " + availability.publisherId + ", id: " + availability.id, ", inline: " + isInline); const generateTimeSlots = (start, end, increment, item) => { const slots = []; // Ensure we're working with the correct date base const baseDate = new Date(item?.startTime || new Date()); baseDate.setHours(start, 0, 0, 0); // Set start time on the base date let currentTime = baseDate.getTime(); const endDate = new Date(item?.startTime || new Date()); endDate.setHours(end, 0, 0, 0); // Set end time on the same date const endTime = endDate.getTime(); // Parse availability's startTime and endTime into Date objects for comparison const itemStartDate = new Date(item?.startTime); const itemEndDate = new Date(item?.endTime); while (currentTime < endTime) { let slotStart = new Date(currentTime); let slotEnd = new Date(currentTime + increment * 60 * 60 * 1000); // Calculate slot end time // Check if the slot overlaps with the availability's time range const isChecked = slotStart < itemEndDate && slotEnd > itemStartDate; slots.push({ startTime: slotStart, endTime: slotEnd, isChecked: isChecked, }); currentTime += increment * 60 * 60 * 1000; // Move to the next slot } slots[0].isFirst = true; slots[slots.length - 1].isLast = true; slots[0].isWithTransport = item.isWithTransportIn; slots[slots.length - 1].isWithTransport = item.isWithTransportOut; return slots; }; const TimeSlotCheckboxes = ({ slots, setSlots, item }) => { const [allDay, setAllDay] = useState(false); const handleAllDayChange = (e) => { const updatedSlots = slots.map(slot => ({ ...slot, isChecked: e.target.checked, })); setSlots(updatedSlots); 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; } 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 = `${slot.startTime.getHours()}:${slot.startTime.getMinutes() === 0 ? '00' : slot.startTime.getMinutes()} до ${slot.endTime.getHours()}:${slot.endTime.getMinutes() === 0 ? '00' : slot.endTime.getMinutes()}`; 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 && ( )}
); })} ); }; useEffect(() => { setTimeSlots(generateTimeSlots(9, 18, 1.5, availability)); }, []); return (

{availability.id ? "Редактирай" : "Създай"} Достъпност

setAvailability({ ...availability, endTime: value })} />
{config?.checkboxUI && config.checkboxUI.enabled ? (
{/* Time slot checkboxes */}
) : ( <> {/* Start Time Picker */}
setAvailability({ ...availability, startTime: value })} />
{/* End Time Picker */}
setAvailability({ ...availability, endTime: value })} />
)}
{availability.repeatWeekly && (
setAvailability({ ...availability, endDate: value })} />
)}
{/* */}
{availability.id && ( <> )}
); }