fix calendar, separate AvForm UI
This commit is contained in:
@ -102,7 +102,8 @@ npm install -g next
|
|||||||
next build
|
next build
|
||||||
next start
|
next start
|
||||||
## ------------------------------- dev -----------------------------------###
|
## ------------------------------- dev -----------------------------------###
|
||||||
|
#get docker logs
|
||||||
|
sudo docker logs pw-nextjs-app-1 >> ./app.log
|
||||||
|
|
||||||
# aider:
|
# aider:
|
||||||
export OPENAI_API_KEY=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 # personal
|
export OPENAI_API_KEY=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 # personal
|
||||||
|
@ -16,6 +16,7 @@ import bg from 'date-fns/locale/bg'; // Bulgarian locale
|
|||||||
|
|
||||||
import { bgBG } from '../x-date-pickers/locales/bgBG'; // Your custom translation file
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -181,7 +182,6 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
|
|
||||||
delete availability.date; //remove date from availability as it is not part of the db model
|
delete availability.date; //remove date from availability as it is not part of the db model
|
||||||
// ---------------------- CB UI --------------
|
// ---------------------- CB UI --------------
|
||||||
if (config.checkboxUI.enabled) {
|
|
||||||
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);
|
||||||
@ -259,45 +259,21 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
handleCompletion(avToStore); // Assuming `handleCompletion` is defined to handle post-save logic
|
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);
|
handleCompletion(availability);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Нещо се обърка. Моля, опитайте отново по-късно.");
|
alert("Нещо се обърка. Моля, опитайте отново по-късно.");
|
||||||
toast.error("Нещо се обърка. Моля, опитайте отново по-късно.");
|
toast.error("Нещо се обърка. Моля, опитайте отново по-късно.");
|
||||||
console.error(error.message);
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = async (e) => {
|
const handleDelete = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -346,7 +322,6 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
const itemStartDate = new Date(item?.startTime);
|
const itemStartDate = new Date(item?.startTime);
|
||||||
const itemEndDate = new Date(item?.endTime);
|
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); // Calculate slot end time
|
||||||
@ -370,7 +345,6 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
return slots;
|
return slots;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const TimeSlotCheckboxes = ({ slots, setSlots, item }) => {
|
const TimeSlotCheckboxes = ({ slots, setSlots, item }) => {
|
||||||
const [allDay, setAllDay] = useState(false);
|
const [allDay, setAllDay] = useState(false);
|
||||||
|
|
||||||
@ -474,39 +448,13 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<DatePicker label="Изберете дата" value={availability.startTime} onChange={(value) => setAvailability({ ...availability, endTime: value })} />
|
<DatePicker label="Изберете дата" value={availability.startTime} onChange={(value) => setAvailability({ ...availability, endTime: value })} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{config?.checkboxUI && config.checkboxUI.enabled ? (
|
|
||||||
<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} item={availability} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{/* Start Time Picker */}
|
|
||||||
<div className="mb-2">
|
|
||||||
<MobileTimePicker label="От" minutesStep={15} value={availability.startTime} minTime={minTime} maxTime={maxTime}
|
|
||||||
onChange={(value) => setAvailability({ ...availability, startTime: value })} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* End Time Picker */}
|
|
||||||
<div className="mb-2">
|
|
||||||
<MobileTimePicker label="До" minutesStep={15} value={availability.endTime} minTime={minTime} maxTime={maxTime}
|
|
||||||
onChange={(value) => setAvailability({ ...availability, endTime: value })} />
|
|
||||||
</div>
|
|
||||||
<div className="mb-2">
|
|
||||||
<label className="checkbox-container">
|
|
||||||
<input type="checkbox" checked={availability.isWithTransport} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
|
||||||
onChange={() => setAvailability({ ...availability, isWithTransport: !availability.isWithTransport })} />
|
|
||||||
мога да взема/върна количките
|
|
||||||
<span className="checkmark"></span>
|
|
||||||
</label>
|
|
||||||
</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={availability.repeatWeekly} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
||||||
@ -577,4 +525,3 @@ export default function AvailabilityForm({ publisherId, existingItem, inline, on
|
|||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
580
components/availability/AvailabilityFormDatePicker.js
Normal file
580
components/availability/AvailabilityFormDatePicker.js
Normal file
@ -0,0 +1,580 @@
|
|||||||
|
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: 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
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
// ---------------------- 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);
|
||||||
|
setAllDay(e.target.checked)
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<label className="checkbox-container flex items-center mb-2">
|
||||||
|
<input type="checkbox" checked={allDay} onChange={e => handleAllDayChange(e)} className="form-checkbox h-5 w-5 text-gray-600 mx-2" />
|
||||||
|
Цял ден
|
||||||
|
<span className="checkmark"></span>
|
||||||
|
</label>
|
||||||
|
{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 (
|
||||||
|
<div key={index} className="mb-1 flex justify-between items-center">
|
||||||
|
<label className={`checkbox-container flex items-center mb-2 ${allDay ? 'opacity-50' : ''}`}>
|
||||||
|
<input type="checkbox" checked={slot.isChecked || allDay} onChange={() => handleSlotCheckedChange(slot)}
|
||||||
|
disabled={allDay}
|
||||||
|
className="form-checkbox h-5 w-5 text-gray-600 mx-2" />
|
||||||
|
{slotLabel}
|
||||||
|
<span className="checkmark"></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Conditionally render transport checkbox based on slot being first or last */}
|
||||||
|
{slot.transportNeeded && (
|
||||||
|
<label className={`checkbox-container flex items-center ${(!slot.isChecked || allDay) ? 'opacity-50' : ''}`}>
|
||||||
|
<input type="checkbox"
|
||||||
|
className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
||||||
|
checked={slot.isWithTransport}
|
||||||
|
disabled={!slot.isChecked || allDay}
|
||||||
|
onChange={() => handleTransportChange(slot)} />
|
||||||
|
{slot.isFirst ? 'Вземане' : 'Връщане'}
|
||||||
|
<span className="checkmark"></span>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeSlots(generateTimeSlots(9, 18, 1.5, availability));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: isMobile ? '90%' : 'max-w-xs', margin: '0 auto' }} >
|
||||||
|
<ToastContainer></ToastContainer>
|
||||||
|
<form id="formAv" className="form p-5 bg-white shadow-md rounded-lg p-8 pr-12" onSubmit={handleSubmit}>
|
||||||
|
<h3 className="text-xl font-semibold mb-5 text-gray-800 border-b pb-2">
|
||||||
|
{availability.id ? "Редактирай" : "Създай"} Достъпност
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDateFns} localeText={bgBG} adapterLocale={bg}>
|
||||||
|
<div className="mb-2">
|
||||||
|
<DatePicker label="Изберете дата" value={availability.startTime} onChange={(value) => setAvailability({ ...availability, endTime: value })} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{config?.checkboxUI && config.checkboxUI.enabled ? (
|
||||||
|
<div className="mb-1">
|
||||||
|
{/* Time slot checkboxes */}
|
||||||
|
<TimeSlotCheckboxes slots={timeSlots} setSlots={setTimeSlots} item={availability} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Start Time Picker */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<MobileTimePicker label="От" minutesStep={15} value={availability.startTime} minTime={minTime} maxTime={maxTime}
|
||||||
|
onChange={(value) => setAvailability({ ...availability, startTime: value })} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* End Time Picker */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<MobileTimePicker label="До" minutesStep={15} value={availability.endTime} minTime={minTime} maxTime={maxTime}
|
||||||
|
onChange={(value) => setAvailability({ ...availability, endTime: value })} />
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="checkbox-container">
|
||||||
|
<input type="checkbox" checked={availability.isWithTransport} className="form-checkbox h-5 w-5 text-gray-600 mx-2"
|
||||||
|
onChange={() => setAvailability({ ...availability, isWithTransport: !availability.isWithTransport })} />
|
||||||
|
мога да взема/върна количките
|
||||||
|
<span className="checkmark"></span>
|
||||||
|
</label>
|
||||||
|
</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 })} />
|
||||||
|
Повтаряй всяка {' '}
|
||||||
|
{/* {availability.repeatWeekly && (
|
||||||
|
<select
|
||||||
|
style={{
|
||||||
|
appearance: 'none',
|
||||||
|
MozAppearance: 'none',
|
||||||
|
WebkitAppearance: 'none',
|
||||||
|
border: 'black solid 1px',
|
||||||
|
background: 'transparent',
|
||||||
|
padding: '0 4px',
|
||||||
|
margin: '0 2px',
|
||||||
|
height: 'auto',
|
||||||
|
fontSize: '16px', // Adjust to match surrounding text
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'inherit',
|
||||||
|
}}
|
||||||
|
// 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) })}
|
||||||
|
>
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
</select>
|
||||||
|
)} */}
|
||||||
|
седмица
|
||||||
|
<span className="checkmark"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{false && availability.repeatWeekly && (
|
||||||
|
|
||||||
|
<div className="mb-2">
|
||||||
|
<DatePicker label="До" value={availability.endDate} onChange={(value) => setAvailability({ ...availability, endDate: 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="panel-actions">
|
||||||
|
<button className="action-button" onClick={() => handleCompletion()}> обратно </button>
|
||||||
|
{availability.id && (
|
||||||
|
<><button className="button 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 ? "Обнови" : "Запиши"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -239,8 +239,14 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
//refresh the events from the server
|
//refresh the events from the server
|
||||||
let events = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisherId}`);
|
let e = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisherId}`);
|
||||||
var newEvents = events.data;
|
var newEvents = e.data;
|
||||||
|
// set start and end to Date objects for all events. Fix for the calendar component
|
||||||
|
newEvents.forEach(event => {
|
||||||
|
event.startTime = new Date(event.startTime);
|
||||||
|
event.endTime = new Date(event.endTime);
|
||||||
|
|
||||||
|
});
|
||||||
setEvents(newEvents);
|
setEvents(newEvents);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -285,6 +285,9 @@ export async function filterPublishers(selectFields, searchText, filterDate, fet
|
|||||||
|
|
||||||
if (!filterDate) {
|
if (!filterDate) {
|
||||||
useDateFilter = false;
|
useDateFilter = false;
|
||||||
|
previousMonthStart = new Date();
|
||||||
|
previousMonthStart.setDate(1);
|
||||||
|
previousMonthStart.setMonth(previousMonthStart.getMonth() - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
@ -517,7 +520,15 @@ export async function filterPublishers(selectFields, searchText, filterDate, fet
|
|||||||
where: whereClause,
|
where: whereClause,
|
||||||
select: {
|
select: {
|
||||||
...selectBase,
|
...selectBase,
|
||||||
...(fetchAvailabilities && { availabilities: true })
|
...(fetchAvailabilities && { availabilities: true }),
|
||||||
|
// ...(fetchAssignments && {
|
||||||
|
// assignments: {
|
||||||
|
// include: {
|
||||||
|
// shift: true,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user