rewrite availability form
This commit is contained in:
@ -1,70 +1,39 @@
|
|||||||
import axiosInstance from '../../src/axiosSecure';
|
import axiosInstance from '../../src/axiosSecure';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useRouter } from "next/router";
|
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 { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
|
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
|
||||||
// import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
import bg from 'date-fns/locale/bg';
|
||||||
|
import { bgBG } from '../x-date-pickers/locales/bgBG';
|
||||||
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';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import axios from 'axios';
|
const common = require('src/helpers/common');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
const config = await import('../../config.json');
|
const config = await import('../../config.json');
|
||||||
return config.default;
|
return config.default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
export default function AvailabilityForm({ publisherId, existingItems, inline, onDone, date }) {
|
||||||
// ------------------ 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")
|
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);
|
||||||
|
|
||||||
*/
|
const [timeSlots, setTimeSlots] = useState([]);
|
||||||
|
const [availabilities, setAvailabilities] = useState(existingItems && existingItems.length > 0 ? existingItems : [{
|
||||||
//enum for abailability type - day of week or day of month; and array of values
|
publisherId: publisher.id,
|
||||||
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: "Нов",
|
name: "Нов",
|
||||||
dayofweek: "Monday",
|
dayofweek: "Monday",
|
||||||
dayOfMonth: null,
|
dayOfMonth: null,
|
||||||
@ -74,47 +43,20 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
repeatWeekly: false,
|
repeatWeekly: false,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
isFirst: false,
|
isFirst: false,
|
||||||
});
|
isLast: false,
|
||||||
const [items, setItems] = useState(itemsForDay || []); // [existingItem, ...items]
|
}]);
|
||||||
|
|
||||||
const [selectedType, setSelectedOption] = useState(AvailabilityType.WeeklyRecurrance);
|
|
||||||
const [isInline, setInline] = useState(inline || false);
|
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);
|
const [config, setConfig] = useState(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchConfig().then(config => {
|
fetchConfig().then(config => {
|
||||||
// Use config here to adjust form fields
|
|
||||||
console.log("UI config: ", config);
|
console.log("UI config: ", config);
|
||||||
setConfig(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
|
// Define the minimum and maximum times
|
||||||
const minTime = new Date();
|
const minTime = new Date();
|
||||||
@ -123,66 +65,77 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
maxTime.setHours(20, 0, 0, 0); // 8:00 PM
|
maxTime.setHours(20, 0, 0, 0); // 8:00 PM
|
||||||
|
|
||||||
|
|
||||||
//always setAvailability publisherId
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
availability.publisherId = publisherId;
|
const fetchItemFromDB = async () => {
|
||||||
console.log("availability.publisherId: ", availability.publisherId);
|
const id = parseInt(router.query.id);
|
||||||
}, [availability]);
|
if (existingItems.length == 0 && id) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
console.log("fetching availability " + id);
|
const response = await axiosInstance.get(`/api/data/availabilities/${id}`);
|
||||||
const { data } = await axiosInstance.get(urls.apiUrl + id);
|
setAvailabilities([response.data]);
|
||||||
data.startTime = formatTime(data.startTime);
|
setEditMode(true);
|
||||||
data.endTime = formatTime(data.endTime);
|
|
||||||
setAvailability(data);
|
|
||||||
console.log(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
toast.error("Error fetching availability data.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = ({ target }) => {
|
fetchItemFromDB();
|
||||||
// const { name, value } = e.target;
|
}, [router.query.id]);
|
||||||
// setItem((prev) => ({ ...prev, [name]: value }));
|
|
||||||
console.log("AvailabilityForm: handleChange: " + target.name + " = " + target.value);
|
|
||||||
setAvailability({ ...availability, [target.name]: target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
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) {
|
if (shouldRecreate) {
|
||||||
// availability.name = "От календара";
|
// Delete existing availabilities if they have an ID
|
||||||
availability.name = common.getTimeFomatted(availability.startTime) + "-" + common.getTimeFomatted(availability.endTime);
|
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);
|
// Create new availabilities
|
||||||
if (availability.repeatWeekly) {
|
const createdAvailabilities = await Promise.all(groupedTimeSlots.map(async group => {
|
||||||
availability.dayOfMonth = null;
|
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 {
|
} else {
|
||||||
availability.endDate = null;
|
// Update existing availabilities
|
||||||
availability.dayOfMonth = availability.startTime.getDate();
|
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
|
handleCompletion({ updated: true });
|
||||||
// ---------------------- CB UI --------------
|
} 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);
|
const selectedSlots = timeSlots.filter(slot => slot.isChecked);
|
||||||
// Sort the selected intervals by start time
|
// Sort the selected intervals by start time
|
||||||
const sortedSlots = [...selectedSlots].sort((a, b) => a.startTime - b.startTime);
|
const sortedSlots = [...selectedSlots].sort((a, b) => a.startTime - b.startTime);
|
||||||
@ -209,83 +162,65 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
if (currentGroup.length > 0) {
|
if (currentGroup.length > 0) {
|
||||||
groupedIntervals.push(currentGroup);
|
groupedIntervals.push(currentGroup);
|
||||||
}
|
}
|
||||||
|
return groupedIntervals;
|
||||||
// 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 firstSlotWithTransport = timeSlots[0].checked && timeSlots[0]?.isWithTransport;
|
||||||
// const lastSlotWithTransport = timeSlots[timeSlots.length - 1].checked && timeSlots[timeSlots.length - 1]?.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);
|
||||||
|
|
||||||
availabilities.forEach(async av => {
|
return {
|
||||||
// expand availability
|
name: common.getTimeFomatted(startTime) + "-" + common.getTimeFomatted(endTime),
|
||||||
const avToStore = {
|
publisherId: publisher.id,
|
||||||
...availability,
|
startTime: startTime,
|
||||||
...av,
|
endTime: endTime,
|
||||||
startTime: av.startTime,
|
isWithTransportIn: group[0].isFirst && timeSlots[0].isWithTransport,
|
||||||
endTime: av.endTime,
|
isWithTransportOut: group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport,
|
||||||
name: "От календара",
|
dayofweek: common.getDayOfWeekNameEnEnum(day.getDay()),
|
||||||
id: undefined,
|
repeatWeekly: doRepeat,
|
||||||
|
dayOfMonth: doRepeat ? null : startTime.getDate(),
|
||||||
// isWithTransportIn: firstSlotWithTransport,
|
endDate: doRepeat ? repeatUntil : null,
|
||||||
// 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);
|
|
||||||
} 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 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) => {
|
const handleDelete = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
|
const deletePromises = availabilities.map(async (availability) => {
|
||||||
if (availability.id) {
|
if (availability.id) {
|
||||||
// console.log("deleting publisher id = ", router.query.id, "; url=" + urls.apiUrl + router.query.id);
|
// console.log("deleting publisher id = ", router.query.id, "; url=" + urls.apiUrl + router.query.id);
|
||||||
await axiosInstance.delete(urls.apiUrl + availability.id);
|
await axiosInstance.delete(urls.apiUrl + availability.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(deletePromises);
|
||||||
toast.success("Записът изтрит", {
|
toast.success("Записът изтрит", {
|
||||||
position: "bottom-center",
|
position: "bottom-center",
|
||||||
});
|
});
|
||||||
handleCompletion({ deleted: true }); // Assuming handleCompletion is defined and properly handles post-deletion logic
|
if (handleCompletion) {
|
||||||
|
handleCompletion({ deleted: true });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Нещо се обърка при изтриването. Моля, опитайте отново или се свържете с нас");
|
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 = [];
|
const slots = [];
|
||||||
// Ensure we're working with the correct date base
|
const baseDate = new Date(day || new Date());
|
||||||
const baseDate = new Date(item?.startTime || new Date());
|
baseDate.setHours(start, 0, 0, 0); // Initialize base date with start hour
|
||||||
baseDate.setHours(start, 0, 0, 0); // Set start time on the base date
|
|
||||||
let currentTime = baseDate.getTime();
|
let currentTime = baseDate.getTime();
|
||||||
|
|
||||||
const endDate = new Date(item?.startTime || new Date());
|
// Assuming end time is the same for all items, otherwise, this logic needs adjustment
|
||||||
endDate.setHours(end, 0, 0, 0); // Set end time on the same date
|
const endDate = new Date(day || new Date());
|
||||||
|
endDate.setHours(end, 0, 0, 0);
|
||||||
const endTime = endDate.getTime();
|
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) {
|
while (currentTime < endTime) {
|
||||||
let slotStart = new Date(currentTime);
|
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
|
// Determine if the slot is checked based on overlapping with any item time ranges
|
||||||
const isChecked = slotStart < itemEndDate && slotEnd > itemStartDate;
|
const isChecked = items.some(item => {
|
||||||
|
const itemStartTime = new Date(item.startTime);
|
||||||
|
const itemEndTime = new Date(item.endTime);
|
||||||
|
return slotStart < itemEndTime && slotEnd > itemStartTime;
|
||||||
|
});
|
||||||
|
|
||||||
slots.push({
|
slots.push({
|
||||||
startTime: slotStart,
|
startTime: slotStart,
|
||||||
endTime: slotEnd,
|
endTime: slotEnd,
|
||||||
isChecked: isChecked,
|
isChecked: isChecked,
|
||||||
});
|
});
|
||||||
currentTime += increment * 60 * 60 * 1000; // Move to the next slot
|
|
||||||
|
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[0].isFirst = true;
|
||||||
slots[slots.length - 1].isLast = true;
|
slots[slots.length - 1].isLast = true;
|
||||||
slots[0].isWithTransport = item.isWithTransportIn;
|
// Assuming isWithTransport flags are global settings, not unique per slot
|
||||||
slots[slots.length - 1].isWithTransport = item.isWithTransportOut;
|
slots[0].isWithTransport = items[0]?.isWithTransportIn;
|
||||||
|
slots[slots.length - 1].isWithTransport = items[items.length - 1]?.isWithTransportOut;
|
||||||
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TimeSlotCheckboxes = ({ slots, setSlots, item }) => {
|
const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => {
|
||||||
const [allDay, setAllDay] = useState(false);
|
const [allDay, setAllDay] = useState(false);
|
||||||
|
|
||||||
const handleAllDayChange = (e) => {
|
const handleAllDayChange = (e) => {
|
||||||
@ -378,6 +318,10 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
if ((changedSlot.isFirst || changedSlot.isLast) && !changedSlot.isChecked) {
|
if ((changedSlot.isFirst || changedSlot.isLast) && !changedSlot.isChecked) {
|
||||||
changedSlot.isWithTransport = false;
|
changedSlot.isWithTransport = false;
|
||||||
}
|
}
|
||||||
|
//if no slots are checked, disable Update button
|
||||||
|
const anyChecked = updatedSlots.some(slot => slot.isChecked);
|
||||||
|
setCanUpdate(anyChecked);
|
||||||
|
|
||||||
setSlots(updatedSlots);
|
setSlots(updatedSlots);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -434,35 +378,34 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeSlots(generateTimeSlots(9, 18, 1.5, availability));
|
setTimeSlots(generateTimeSlots(9, 18, 1.5, availabilities));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <div style={{ width: isMobile ? '90%' : 'max-w-xs', margin: '0 auto' }} >
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ToastContainer></ToastContainer>
|
<ToastContainer></ToastContainer>
|
||||||
<form id="formAv" className="form p-5 bg-white shadow-md rounded-lg" onSubmit={handleSubmit}>
|
<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">
|
<h3 className="text-xl font-semibold mb-5 text-gray-800 border-b pb-2">
|
||||||
{availability.id ? "Редактирай" : "Нова"} възможност
|
{editMode ? "Редактирай" : "Нова"} възможност
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDateFns} localeText={bgBG} adapterLocale={bg}>
|
<LocalizationProvider dateAdapter={AdapterDateFns} localeText={bgBG} adapterLocale={bg}>
|
||||||
<div className="mb-2">
|
<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>
|
<div>
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
{/* Time slot checkboxes */}
|
{/* Time slot checkboxes */}
|
||||||
<TimeSlotCheckboxes slots={timeSlots} setSlots={setTimeSlots} item={availability} />
|
<TimeSlotCheckboxes slots={timeSlots} setSlots={setTimeSlots} items={availabilities} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<label className="checkbox-container">
|
<label className="checkbox-container">
|
||||||
<input type="checkbox" checked={availability.repeatWeekly} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
<input type="checkbox" checked={doRepeat} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
||||||
onChange={() => setAvailability({ ...availability, repeatWeekly: !availability.repeatWeekly })} />
|
onChange={(e) => setDoRepeat(e.target.checked)} />
|
||||||
Повтаряй всяка {' '}
|
Повтаряй всяка {' '}
|
||||||
{/* {availability.repeatWeekly && (
|
{/* {repeatWeekly && (
|
||||||
<select
|
<select
|
||||||
style={{
|
style={{
|
||||||
appearance: 'none',
|
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="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"
|
//className="form-select mx-2 h-8 text-gray-600"
|
||||||
value={availability.repeatFrequency || 1}
|
value={repeatFrequency || 1}
|
||||||
onChange={(e) => setAvailability({ ...availability, repeatFrequency: parseInt(e.target.value, 10) })}
|
onChange={(e) => setRepeatFrequency(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="1">1</option>
|
<option value="1">1</option>
|
||||||
<option value="2">2</option>
|
<option value="2">2</option>
|
||||||
@ -494,33 +437,26 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{false && availability.repeatWeekly && (
|
{false && repeatWeekly && (
|
||||||
|
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<DatePicker label="До" value={availability.endDate} onChange={(value) => setAvailability({ ...availability, endDate: value })} />
|
<DatePicker label="До" value={repeatUntil} onChange={(value) => setRepeatUntil({ value })} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</LocalizationProvider>
|
</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">
|
<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>
|
<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 className="button btn-outline bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={handleDelete}>
|
||||||
Изтрий
|
Изтрий
|
||||||
</button></>
|
</button></>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline"
|
className={`button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline ${!canUpdate ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
> {availability.id ? "Обнови" : "Запиши"}
|
disabled={!canUpdate}
|
||||||
|
> {editMode ? "Обнови" : "Запиши"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -95,27 +95,19 @@ export default function AvailabilityList({ publisher, showNew }) {
|
|||||||
<AvailabilityForm
|
<AvailabilityForm
|
||||||
publisherId={publisher.id}
|
publisherId={publisher.id}
|
||||||
inline={true}
|
inline={true}
|
||||||
existingItem={selectedItem}
|
existingItems={selectedItem ? [selectedItem] : []}
|
||||||
|
date={selectedItem ? new Date(selectedItem.startTime) : new Date()}
|
||||||
onDone={(item) => {
|
onDone={(item) => {
|
||||||
toggleAv();
|
toggleAv();
|
||||||
setSelectedItem(null);
|
setSelectedItem(null);
|
||||||
if (!item) {
|
//get the updated list of availabilities from the server
|
||||||
// remove selected item from state
|
axiosInstance.get(common.getBaseUrl("/api/data/availabilities"))
|
||||||
const updatedItems = items.filter(i => i.id !== selectedItem.id);
|
.then(({ data: items }) => {
|
||||||
setItems([...updatedItems]);
|
setItems(items);
|
||||||
return;
|
})
|
||||||
};
|
.catch(error => {
|
||||||
const itemIndex = items.findIndex(i => i.id === item.id); // assuming each item has a unique 'id' property
|
console.error("Error getting availabilities:", error);
|
||||||
|
});
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -16,6 +16,7 @@ import { MdToday } from 'react-icons/md';
|
|||||||
|
|
||||||
import { useSwipeable } from 'react-swipeable';
|
import { useSwipeable } from 'react-swipeable';
|
||||||
import axiosInstance from '../../src/axiosSecure';
|
import axiosInstance from '../../src/axiosSecure';
|
||||||
|
import { set } from 'date-fns';
|
||||||
|
|
||||||
// Set moment to use the Bulgarian locale
|
// Set moment to use the Bulgarian locale
|
||||||
moment.locale('bg');
|
moment.locale('bg');
|
||||||
@ -43,7 +44,7 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
const [evts, setEvents] = useState(events); // Existing events
|
const [evts, setEvents] = useState(events); // Existing events
|
||||||
const [displayedEvents, setDisplayedEvents] = useState(evts); // Events to display in the calendar
|
const [displayedEvents, setDisplayedEvents] = useState(evts); // Events to display in the calendar
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
const [selectedEvents, setSelectedEvents] = useState([]);
|
||||||
const [visibleRange, setVisibleRange] = useState(() => {
|
const [visibleRange, setVisibleRange] = useState(() => {
|
||||||
const start = new Date();
|
const start = new Date();
|
||||||
start.setDate(1); // Set to the first day of the current month
|
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
|
// get exising events for the selected date
|
||||||
const existingEvents = evts?.filter(event => event.publisherId === publisherId && event.startTime === start.toDateString());
|
const existingEvents = evts?.filter(event => event.publisherId === publisherId && event.startTime === start.toDateString());
|
||||||
|
|
||||||
setSelectedEvent({
|
console.log("handleSelect: " + existingEvents);
|
||||||
date: start,
|
setSelectedEvents(existingEvents);
|
||||||
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,
|
|
||||||
|
|
||||||
});
|
// 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);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -214,7 +218,7 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
delete eventForEditing.type;
|
delete eventForEditing.type;
|
||||||
delete eventForEditing.publisher
|
delete eventForEditing.publisher
|
||||||
console.log("handleEventClick: " + eventForEditing);
|
console.log("handleEventClick: " + eventForEditing);
|
||||||
setSelectedEvent(eventForEditing);
|
setSelectedEvents([eventForEditing]);
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -223,22 +227,6 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
if (dialogEvent === null || dialogEvent === undefined) {
|
if (dialogEvent === null || dialogEvent === undefined) {
|
||||||
} else {
|
} 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}`);
|
let e = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisherId}`);
|
||||||
var newEvents = e.data;
|
var newEvents = e.data;
|
||||||
// set start and end to Date objects for all events. Fix for the calendar component
|
// 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">
|
<div className="modal-content">
|
||||||
<AvailabilityForm
|
<AvailabilityForm
|
||||||
publisherId={publisherId}
|
publisherId={publisherId}
|
||||||
existingItem={selectedEvent}
|
existingItems={selectedEvents}
|
||||||
|
date={date}
|
||||||
onDone={handleDialogClose}
|
onDone={handleDialogClose}
|
||||||
inline={true}
|
inline={true}
|
||||||
// Pass other props as needed
|
// Pass other props as needed
|
||||||
|
@ -14,8 +14,6 @@ export default function NewPage(props: ICartEventPageProps) {
|
|||||||
<Layout>
|
<Layout>
|
||||||
<div className="h-5/6 grid place-items-center">
|
<div className="h-5/6 grid place-items-center">
|
||||||
<CartEventForm props={props} />
|
<CartEventForm props={props} />
|
||||||
{/*
|
|
||||||
<AvailabilityForm id={item.id} publisherId={item.publisherId} /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user