rewrite availability form
This commit is contained in:
@ -1,70 +1,39 @@
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useCallback } 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 bg from 'date-fns/locale/bg';
|
||||
import { bgBG } from '../x-date-pickers/locales/bgBG';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import axios from 'axios';
|
||||
|
||||
|
||||
const common = require('src/helpers/common');
|
||||
|
||||
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
|
||||
export default function AvailabilityForm({ publisherId, existingItems, inline, onDone, date }) {
|
||||
|
||||
@@map("Availability")
|
||||
}
|
||||
const router = useRouter();
|
||||
const urls = {
|
||||
apiUrl: "/api/data/availabilities/",
|
||||
indexUrl: "/cart/availabilities"
|
||||
};
|
||||
|
||||
const [editMode, setEditMode] = useState(existingItems.length > 0);
|
||||
const [publisher, setPublisher] = useState({ id: publisherId });
|
||||
const [day, setDay] = useState(new Date(date || new Date()));
|
||||
const [doRepeat, setDoRepeat] = useState(false);
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||
const [repeatUntil, setRepeatUntil] = useState(null);
|
||||
const [canUpdate, setCanUpdate] = useState(true);
|
||||
|
||||
*/
|
||||
|
||||
//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,
|
||||
const [timeSlots, setTimeSlots] = useState([]);
|
||||
const [availabilities, setAvailabilities] = useState(existingItems && existingItems.length > 0 ? existingItems : [{
|
||||
publisherId: publisher.id,
|
||||
name: "Нов",
|
||||
dayofweek: "Monday",
|
||||
dayOfMonth: null,
|
||||
@ -74,47 +43,20 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
repeatWeekly: false,
|
||||
endDate: null,
|
||||
isFirst: false,
|
||||
});
|
||||
const [items, setItems] = useState(itemsForDay || []); // [existingItem, ...items]
|
||||
isLast: false,
|
||||
}]);
|
||||
|
||||
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();
|
||||
@ -123,169 +65,162 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
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
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Error fetching availability data.");
|
||||
}
|
||||
}
|
||||
}, [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);
|
||||
}
|
||||
};
|
||||
fetchItemFromDB();
|
||||
}, [router.query.id]);
|
||||
|
||||
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 {
|
||||
const groupedTimeSlots = mergeCheckedTimeSlots(timeSlots);
|
||||
|
||||
// Determine if we need to delete and recreate, or just update
|
||||
const shouldRecreate = availabilities.length !== groupedTimeSlots.length || availabilities.some(av => !av.id);
|
||||
|
||||
if (!availability.name) {
|
||||
// availability.name = "От календара";
|
||||
availability.name = common.getTimeFomatted(availability.startTime) + "-" + common.getTimeFomatted(availability.endTime);
|
||||
}
|
||||
if (shouldRecreate) {
|
||||
// Delete existing availabilities if they have an ID
|
||||
console.log("Recreating availabilities");
|
||||
await Promise.all(availabilities.filter(av => av.id).map(av => axiosInstance.delete(`${urls.apiUrl}${av.id}`)));
|
||||
|
||||
availability.dayofweek = common.getDayOfWeekNameEnEnum(availability.startTime);
|
||||
if (availability.repeatWeekly) {
|
||||
availability.dayOfMonth = null;
|
||||
// Create new availabilities
|
||||
const createdAvailabilities = 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(createdAvailabilities);
|
||||
} else {
|
||||
availability.endDate = null;
|
||||
availability.dayOfMonth = availability.startTime.getDate();
|
||||
// Update existing availabilities
|
||||
console.log("Updating existing availabilities");
|
||||
const updatedAvailabilities = await Promise.all(availabilities.map(async (availability, index) => {
|
||||
const group = groupedTimeSlots[index];
|
||||
const updatedAvailability = updateAvailabilityFromGroup(availability, group);
|
||||
await axiosInstance.put(`${urls.apiUrl}${availability.id}`, updatedAvailability);
|
||||
return updatedAvailability;
|
||||
}));
|
||||
|
||||
setAvailabilities(updatedAvailabilities);
|
||||
}
|
||||
|
||||
delete availability.date; //remove date from availability as it is not part of the db model
|
||||
// ---------------------- CB UI --------------
|
||||
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: group[0].isFirst && timeSlots[0].isWithTransport,
|
||||
isWithTransportOut: group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].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
|
||||
});
|
||||
|
||||
handleCompletion(availability);
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// const firstSlotWithTransport = timeSlots[0].checked && timeSlots[0]?.isWithTransport;
|
||||
// const lastSlotWithTransport = timeSlots[timeSlots.length - 1].checked && timeSlots[timeSlots.length - 1]?.isWithTransport;
|
||||
function createAvailabilityFromGroup(group) {
|
||||
const startTime = new Date(day);
|
||||
startTime.setTime(group[0].startTime)
|
||||
const endTime = new Date(day);
|
||||
endTime.setTime(group[group.length - 1].endTime);
|
||||
|
||||
return {
|
||||
name: common.getTimeFomatted(startTime) + "-" + common.getTimeFomatted(endTime),
|
||||
publisherId: publisher.id,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
isWithTransportIn: group[0].isFirst && timeSlots[0].isWithTransport,
|
||||
isWithTransportOut: group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport,
|
||||
dayofweek: common.getDayOfWeekNameEnEnum(day.getDay()),
|
||||
repeatWeekly: doRepeat,
|
||||
dayOfMonth: doRepeat ? null : startTime.getDate(),
|
||||
endDate: doRepeat ? repeatUntil : null,
|
||||
};
|
||||
}
|
||||
|
||||
function updateAvailabilityFromGroup(availability, group) {
|
||||
availability.name = common.getTimeFomatted(startTime) + "-" + common.getTimeFomatted(endTime);
|
||||
availability.startTime.setTime(group[0].startTime);
|
||||
availability.endTime.setTime(group[group.length - 1].endTime);
|
||||
|
||||
availability.isWithTransportIn = group[0].isFirst && timeSlots[0].isWithTransport;
|
||||
availability.isWithTransportOut = group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport;
|
||||
|
||||
availability.repeatWeekly = doRepeat;
|
||||
availability.dayOfMonth = doRepeat ? null : group.startTime.getDate();
|
||||
availability.endDate = doRepeat ? repeatUntil : null;
|
||||
|
||||
return availability;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
const deletePromises = availabilities.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("Нещо се обърка при изтриването. Моля, опитайте отново или се свържете с нас");
|
||||
@ -305,48 +240,53 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
}
|
||||
}
|
||||
|
||||
console.log("AvailabilityForm: publisherId: " + availability.publisherId + ", id: " + availability.id, ", inline: " + isInline);
|
||||
// console.log("AvailabilityForm: publisherId: " + publisher.id + ", id: " + availabilit .id, ", inline: " + isInline);
|
||||
|
||||
|
||||
const generateTimeSlots = (start, end, increment, item) => {
|
||||
const generateTimeSlots = (start, end, increment, items) => {
|
||||
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
|
||||
const baseDate = new Date(day || new Date());
|
||||
baseDate.setHours(start, 0, 0, 0); // Initialize base date with start hour
|
||||
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
|
||||
// Assuming end time is the same for all items, otherwise, this logic needs adjustment
|
||||
const endDate = new Date(day || new Date());
|
||||
endDate.setHours(end, 0, 0, 0);
|
||||
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
|
||||
let slotEnd = new Date(currentTime + increment * 60 * 60 * 1000);
|
||||
|
||||
// Check if the slot overlaps with the availability's time range
|
||||
const isChecked = slotStart < itemEndDate && slotEnd > itemStartDate;
|
||||
// Determine if the slot is checked based on overlapping with any item time ranges
|
||||
const isChecked = items.some(item => {
|
||||
const itemStartTime = new Date(item.startTime);
|
||||
const itemEndTime = new Date(item.endTime);
|
||||
return slotStart < itemEndTime && slotEnd > itemStartTime;
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
currentTime += increment * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
// Assign 'isFirst' and 'isLast' based on the slot's position in the array
|
||||
if (slots.length > 0) {
|
||||
slots[0].isFirst = true;
|
||||
slots[slots.length - 1].isLast = true;
|
||||
// Assuming isWithTransport flags are global settings, not unique per slot
|
||||
slots[0].isWithTransport = items[0]?.isWithTransportIn;
|
||||
slots[slots.length - 1].isWithTransport = items[items.length - 1]?.isWithTransportOut;
|
||||
}
|
||||
|
||||
return slots;
|
||||
};
|
||||
|
||||
const TimeSlotCheckboxes = ({ slots, setSlots, item }) => {
|
||||
const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => {
|
||||
const [allDay, setAllDay] = useState(false);
|
||||
|
||||
const handleAllDayChange = (e) => {
|
||||
@ -378,6 +318,10 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
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);
|
||||
};
|
||||
|
||||
@ -434,35 +378,34 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTimeSlots(generateTimeSlots(9, 18, 1.5, availability));
|
||||
setTimeSlots(generateTimeSlots(9, 18, 1.5, availabilities));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
// <div style={{ width: isMobile ? '90%' : 'max-w-xs', margin: '0 auto' }} >
|
||||
<div className="w-full">
|
||||
<ToastContainer></ToastContainer>
|
||||
<form id="formAv" className="form p-5 bg-white shadow-md rounded-lg" onSubmit={handleSubmit}>
|
||||
<h3 className="text-xl font-semibold mb-5 text-gray-800 border-b pb-2">
|
||||
{availability.id ? "Редактирай" : "Нова"} възможност
|
||||
{editMode ? "Редактирай" : "Нова"} възможност
|
||||
</h3>
|
||||
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns} localeText={bgBG} adapterLocale={bg}>
|
||||
<div className="mb-2">
|
||||
<DatePicker label="Изберете дата" value={availability.startTime} onChange={(value) => setAvailability({ ...availability, endTime: value })} />
|
||||
<DatePicker label="Изберете дата" value={day} onChange={(value) => setDay({ value })} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-1">
|
||||
{/* Time slot checkboxes */}
|
||||
<TimeSlotCheckboxes slots={timeSlots} setSlots={setTimeSlots} item={availability} />
|
||||
<TimeSlotCheckboxes slots={timeSlots} setSlots={setTimeSlots} items={availabilities} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<label className="checkbox-container">
|
||||
<input type="checkbox" checked={availability.repeatWeekly} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
||||
onChange={() => setAvailability({ ...availability, repeatWeekly: !availability.repeatWeekly })} />
|
||||
<input type="checkbox" checked={doRepeat} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
||||
onChange={(e) => setDoRepeat(e.target.checked)} />
|
||||
Повтаряй всяка {' '}
|
||||
{/* {availability.repeatWeekly && (
|
||||
{/* {repeatWeekly && (
|
||||
<select
|
||||
style={{
|
||||
appearance: 'none',
|
||||
@ -480,8 +423,8 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
// className="appearance-none border border-black bg-transparent px-1 py-0 mx-0 mr-1 h-auto text-base text-center text-current align-middle cursor-pointer"
|
||||
|
||||
//className="form-select mx-2 h-8 text-gray-600"
|
||||
value={availability.repeatFrequency || 1}
|
||||
onChange={(e) => setAvailability({ ...availability, repeatFrequency: parseInt(e.target.value, 10) })}
|
||||
value={repeatFrequency || 1}
|
||||
onChange={(e) => setRepeatFrequency(e.target.value)}
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
@ -494,33 +437,26 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{false && availability.repeatWeekly && (
|
||||
{false && repeatWeekly && (
|
||||
|
||||
<div className="mb-2">
|
||||
<DatePicker label="До" value={availability.endDate} onChange={(value) => setAvailability({ ...availability, endDate: value })} />
|
||||
<DatePicker label="До" value={repeatUntil} onChange={(value) => setRepeatUntil({ value })} />
|
||||
</div>
|
||||
)}
|
||||
</LocalizationProvider>
|
||||
|
||||
<div className="mb-2 hidden">
|
||||
<div className="form-check">
|
||||
<input className="checkbox form-input" type="checkbox" id="isactive" name="isactive" onChange={handleChange} checked={availability.isactive} autoComplete="off" />
|
||||
<label className="label" htmlFor="isactive">активно</label>
|
||||
</div>
|
||||
</div>
|
||||
{/* <input type="hidden" name="isactive" value={availability.isactive} /> */}
|
||||
|
||||
<div className="flex justify-between items-center flex-nowrap w-full p-1">
|
||||
<button className="button border border-blue-500 text-blue-500 bg-transparent hover:text-white focus:outline-none focus:shadow-outline" onClick={() => handleCompletion()}> Отмени </button>
|
||||
|
||||
{availability.id && (
|
||||
{editMode && (
|
||||
<><button className="button btn-outline bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={handleDelete}>
|
||||
Изтрий
|
||||
</button></>
|
||||
)}
|
||||
<button
|
||||
className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline"
|
||||
> {availability.id ? "Обнови" : "Запиши"}
|
||||
className={`button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline ${!canUpdate ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
disabled={!canUpdate}
|
||||
> {editMode ? "Обнови" : "Запиши"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -95,27 +95,19 @@ export default function AvailabilityList({ publisher, showNew }) {
|
||||
<AvailabilityForm
|
||||
publisherId={publisher.id}
|
||||
inline={true}
|
||||
existingItem={selectedItem}
|
||||
existingItems={selectedItem ? [selectedItem] : []}
|
||||
date={selectedItem ? new Date(selectedItem.startTime) : new Date()}
|
||||
onDone={(item) => {
|
||||
toggleAv();
|
||||
setSelectedItem(null);
|
||||
if (!item) {
|
||||
// remove selected item from state
|
||||
const updatedItems = items.filter(i => i.id !== selectedItem.id);
|
||||
setItems([...updatedItems]);
|
||||
return;
|
||||
};
|
||||
const itemIndex = items.findIndex(i => i.id === item.id); // assuming each item has a unique 'id' property
|
||||
|
||||
if (itemIndex !== -1) {
|
||||
// Replace the existing item with the updated item
|
||||
const updatedItems = [...items];
|
||||
updatedItems[itemIndex] = item;
|
||||
setItems(updatedItems);
|
||||
} else {
|
||||
// Append the new item to the end of the list
|
||||
setItems([...items, item]);
|
||||
}
|
||||
//get the updated list of availabilities from the server
|
||||
axiosInstance.get(common.getBaseUrl("/api/data/availabilities"))
|
||||
.then(({ data: items }) => {
|
||||
setItems(items);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error getting availabilities:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -16,6 +16,7 @@ import { MdToday } from 'react-icons/md';
|
||||
|
||||
import { useSwipeable } from 'react-swipeable';
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import { set } from 'date-fns';
|
||||
|
||||
// Set moment to use the Bulgarian locale
|
||||
moment.locale('bg');
|
||||
@ -43,7 +44,7 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
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 [selectedEvents, setSelectedEvents] = useState([]);
|
||||
const [visibleRange, setVisibleRange] = useState(() => {
|
||||
const start = new Date();
|
||||
start.setDate(1); // Set to the first day of the current month
|
||||
@ -182,18 +183,21 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
// 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,
|
||||
console.log("handleSelect: " + existingEvents);
|
||||
setSelectedEvents(existingEvents);
|
||||
|
||||
});
|
||||
// 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);
|
||||
};
|
||||
|
||||
@ -214,7 +218,7 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
delete eventForEditing.type;
|
||||
delete eventForEditing.publisher
|
||||
console.log("handleEventClick: " + eventForEditing);
|
||||
setSelectedEvent(eventForEditing);
|
||||
setSelectedEvents([eventForEditing]);
|
||||
setIsModalOpen(true);
|
||||
|
||||
};
|
||||
@ -223,22 +227,6 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
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 e = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisherId}`);
|
||||
var newEvents = e.data;
|
||||
// set start and end to Date objects for all events. Fix for the calendar component
|
||||
@ -468,7 +456,8 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
<div className="modal-content">
|
||||
<AvailabilityForm
|
||||
publisherId={publisherId}
|
||||
existingItem={selectedEvent}
|
||||
existingItems={selectedEvents}
|
||||
date={date}
|
||||
onDone={handleDialogClose}
|
||||
inline={true}
|
||||
// Pass other props as needed
|
||||
|
Reference in New Issue
Block a user