initial commit - code moved to separate repo
This commit is contained in:
240
components/calendar/ShiftComponent.tsx
Normal file
240
components/calendar/ShiftComponent.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import PublisherSearchBox from '../publisher/PublisherSearchBox'; // Update the path
|
||||
|
||||
const common = require('src/helpers/common');
|
||||
|
||||
|
||||
interface ModalProps {
|
||||
children: React.ReactNode;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
forDate: Date;
|
||||
useFilterDate: boolean;
|
||||
onUseFilterDateChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
function Modal({ children, isOpen, onClose, forDate, useFilterDate, onUseFilterDateChange }: ModalProps) {
|
||||
if (!isOpen) return null;
|
||||
const isValidDate = forDate instanceof Date && !isNaN(forDate.getTime());
|
||||
console.log("forDate", forDate, isValidDate);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-4 rounded-md shadow-lg modal-content">
|
||||
{isValidDate && (
|
||||
<h2 className="text-xl font-bold mb-2">
|
||||
<label className="cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useFilterDate}
|
||||
onChange={(e) => onUseFilterDateChange(e.target.checked)}
|
||||
/>
|
||||
{` на разположение ${common.getDateFormated(forDate)} или ${common.getDayOfWeekName(forDate)}`}
|
||||
</label>
|
||||
</h2>
|
||||
)}
|
||||
{children}
|
||||
<button type="button" onClick={onClose} className="mt-4 text-red-500">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
<div className="fixed inset-0 bg-black opacity-50 modal-overlay" onClick={onClose}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, allPublishersInfo }) {
|
||||
|
||||
const [assignments, setAssignments] = useState(shift.assignments);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [useFilterDate, setUseFilterDate] = useState(true);
|
||||
const [selectedPublisher, setSelectedPublisher] = useState(null);
|
||||
const [showCopyHint, setShowCopyHint] = useState(false);
|
||||
|
||||
// Update assignments when shift changes
|
||||
useEffect(() => {
|
||||
setAssignments(shift.assignments);
|
||||
}, [shift.assignments]);
|
||||
|
||||
const handleShiftClick = (shiftId) => {
|
||||
// console.log("onShiftSelect prop:", onShiftSelect);
|
||||
// console.log("Shift clicked:", shift);
|
||||
//shift.selectedPublisher = selectedPublisher;
|
||||
if (onShiftSelect) {
|
||||
onShiftSelect(shift);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePublisherClick = (publisher) => {
|
||||
|
||||
//toggle selected
|
||||
// if (selectedPublisher != null) {
|
||||
// setSelectedPublisher(null);
|
||||
// }
|
||||
// else {
|
||||
setSelectedPublisher(publisher);
|
||||
|
||||
|
||||
console.log("Publisher clicked:", publisher, "selected publisher:", selectedPublisher);
|
||||
shift.selectedPublisher = publisher;
|
||||
if (onShiftSelect) {
|
||||
onShiftSelect(shift);
|
||||
}
|
||||
// if (onPublisherSelect) {
|
||||
// onPublisherSelect(publisher);
|
||||
// }
|
||||
}
|
||||
|
||||
const removeAssignment = async (id) => {
|
||||
try {
|
||||
console.log("Removing assignment with id:", id);
|
||||
await axiosInstance.delete("/api/data/assignments/" + id);
|
||||
setAssignments(prevAssignments => prevAssignments.filter(ass => ass.id !== id));
|
||||
} catch (error) {
|
||||
console.error("Error removing assignment:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const addAssignment = async (publisher, shiftId) => {
|
||||
try {
|
||||
console.log(`new assignment for publisher ${publisher.id} - ${publisher.firstName} ${publisher.lastName}`);
|
||||
const newAssignment = {
|
||||
publisher: { connect: { id: publisher.id } },
|
||||
shift: { connect: { id: shiftId } },
|
||||
isactive: true,
|
||||
isConfirmed: true
|
||||
};
|
||||
const { data } = await axiosInstance.post("/api/data/assignments", newAssignment);
|
||||
// Update the 'publisher' property of the returned data with the full publisher object
|
||||
data.publisher = publisher;
|
||||
setAssignments(prevAssignments => [...prevAssignments, data]);
|
||||
} catch (error) {
|
||||
console.error("Error adding assignment:", error);
|
||||
}
|
||||
};
|
||||
const copyAllPublisherNames = () => {
|
||||
const names = assignments.map(ass => `${ass.publisher.firstName} ${ass.publisher.lastName}`).join(', ');
|
||||
common.copyToClipboard(null, names);
|
||||
// Show hint and set a timer to hide it
|
||||
setShowCopyHint(true);
|
||||
setTimeout(() => setShowCopyHint(false), 1500);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={`flow w-full p-4 py-2 border-2 border-gray-300 rounded-md my-1 ${isSelected ? 'bg-gray-200' : ''}`}
|
||||
onClick={handleShiftClick} onDoubleClick={copyAllPublisherNames}>
|
||||
{/* Time Window Header */}
|
||||
<div className="flex justify-between items-center mb-2 border-b pb-1">
|
||||
<span className="text-lg font-semibold">
|
||||
{`${common.getTimeRange(new Date(shift.startTime), new Date(shift.endTime))}`}
|
||||
</span>
|
||||
|
||||
{/* Copy All Names Button */}
|
||||
<button onClick={copyAllPublisherNames} className="bg-green-500 text-white py-1 px-2 text-sm rounded-md">
|
||||
копирай имената {/* Placeholder for Copy icon */}
|
||||
</button>
|
||||
{/* Hint Message */}
|
||||
{showCopyHint && (
|
||||
<div className="absolute top-0 right-0 p-2 bg-green-200 text-green-800 rounded">
|
||||
Имената са копирани
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Assignments */}
|
||||
{assignments.map((ass, index) => {
|
||||
const publisherInfo = allPublishersInfo.find(info => info?.id === ass.publisher.id) || ass.publisher;
|
||||
|
||||
// Determine border styles
|
||||
let borderStyles = '';
|
||||
//if there is no publisherInfo - draw red border - publisher is no longer available for the day!
|
||||
if (!publisherInfo.availabilities || publisherInfo.availabilities.length == 0) {
|
||||
borderStyles = 'border-2 border-red-500 ';
|
||||
}
|
||||
else {
|
||||
//pub is not available for that shift assignment.
|
||||
if (publisherInfo.availabilities?.length === 0 ||
|
||||
publisherInfo.availabilities?.every(avail => avail.isFromPreviousAssignment)) {
|
||||
borderStyles += 'border-l-3 border-r-3 border-orange-500 '; // Top border for manual publishers
|
||||
}
|
||||
// checkig if the publisher is available for this assignment
|
||||
if (publisherInfo.availabilities?.some(av =>
|
||||
av.startTime <= ass.startTime &&
|
||||
av.endTime >= ass.endTime)) {
|
||||
borderStyles += 'border-t-2 border-red-500 '; // Left border for specific availability conditions
|
||||
}
|
||||
|
||||
//the pub is the same time as last month
|
||||
// if (publisherInfo.availabilities?.some(av =>
|
||||
// (!av.dayOfMonth || av.isFromPreviousMonth) &&
|
||||
// av.startTime <= ass.startTime &&
|
||||
// av.endTime >= ass.endTime)) {
|
||||
// borderStyles += 'border-t-2 border-yellow-500 '; // Left border for specific availability conditions
|
||||
// }
|
||||
if (selectedPublisher && selectedPublisher.id === ass.publisher.id) {
|
||||
borderStyles += 'border-2 border-blue-300'; // Bottom border for selected publishers
|
||||
}
|
||||
if (publisherInfo.hasUpToDateAvailabilities) {
|
||||
//add green right border
|
||||
borderStyles += 'border-r-2 border-green-300';
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div key={index}
|
||||
className={`flow space-x-2 rounded-md px-2 py-1 my-1 ${ass.isConfirmed ? 'bg-yellow-100' : 'bg-gray-100'} ${borderStyles}`}
|
||||
>
|
||||
<div className="flex justify-between items-center" onClick={() => handlePublisherClick(ass.publisher)}>
|
||||
<span className="text-gray-700">{publisherInfo.firstName} {publisherInfo.lastName}</span>
|
||||
<button onClick={() => removeAssignment(ass.id)}
|
||||
className="text-white bg-red-500 hover:bg-red-600 px-3 py-1 ml-2 rounded-md"
|
||||
>
|
||||
махни
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
{/* This is a placeholder for the dropdown to add a publisher. You'll need to implement or integrate a dropdown component */}
|
||||
|
||||
<div className="flex space-x-2 items-center">
|
||||
{/* Add Button */}
|
||||
<button onClick={() => setIsModalOpen(true)} className="bg-blue-500 text-white p-2 py-1 rounded-md">
|
||||
добави {/* Placeholder for Add icon */}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Modal for Publisher Search
|
||||
forDate={new Date(shift.startTime)}
|
||||
*/}
|
||||
<Modal isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
forDate={new Date(shift.startTime)}
|
||||
useFilterDate={useFilterDate}
|
||||
onUseFilterDateChange={(value) => setUseFilterDate(value)}>
|
||||
|
||||
<PublisherSearchBox
|
||||
selectedId={null}
|
||||
isFocused={isModalOpen}
|
||||
filterDate={useFilterDate ? new Date(shift.startTime) : null}
|
||||
onChange={(publisher) => {
|
||||
// Add publisher as assignment logic
|
||||
setIsModalOpen(false);
|
||||
addAssignment(publisher, shift.id);
|
||||
}}
|
||||
showAllAuto={true}
|
||||
showSearch={true}
|
||||
showList={false}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ShiftComponent;
|
478
components/calendar/avcalendar.tsx
Normal file
478
components/calendar/avcalendar.tsx
Normal file
@@ -0,0 +1,478 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Calendar, momentLocalizer, dateFnsLocalizer } from 'react-big-calendar';
|
||||
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||
import AvailabilityForm from '../availability/AvailabilityForm';
|
||||
import common from '../../src/helpers/common';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/bg'; // Import Bulgarian locale
|
||||
import { ArrowLeftCircleIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
import { FaArrowLeft, FaArrowRight, FaRegCalendarAlt, FaRegListAlt, FaRegCalendarCheck } from 'react-icons/fa';
|
||||
import { MdToday } from 'react-icons/md';
|
||||
|
||||
import { useSwipeable } from 'react-swipeable';
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
|
||||
// Set moment to use the Bulgarian locale
|
||||
moment.locale('bg');
|
||||
const localizer = momentLocalizer(moment);
|
||||
// Bulgarian translations for Calendar labels
|
||||
const messages = {
|
||||
allDay: 'Цял ден',
|
||||
previous: 'Предишен',
|
||||
next: 'Следващ',
|
||||
today: 'Днес',
|
||||
month: 'Месец',
|
||||
week: 'Седмица',
|
||||
day: 'Ден',
|
||||
agenda: 'Дневен ред',
|
||||
date: 'Дата',
|
||||
time: 'Час',
|
||||
event: 'Събитие', // or 'Събитие' depending on context
|
||||
// Any other labels you want to translate...
|
||||
};
|
||||
|
||||
const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
|
||||
const [date, setDate] = useState(new Date());
|
||||
const [currentView, setCurrentView] = useState('month');
|
||||
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 [visibleRange, setVisibleRange] = useState(() => {
|
||||
const start = new Date();
|
||||
start.setDate(1); // Set to the first day of the current month
|
||||
const end = new Date(start.getFullYear(), start.getMonth() + 1, 0); // Last day of the current month
|
||||
return { start, end };
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Update internal state when `events` prop changes
|
||||
useEffect(() => {
|
||||
setEvents(events);
|
||||
// Call any function here to process and set displayedEvents
|
||||
// based on the new events, if necessary
|
||||
}, [events]);
|
||||
|
||||
const onRangeChange = (range) => {
|
||||
if (Array.isArray(range)) {
|
||||
// For week and day views, range is an array of dates
|
||||
setVisibleRange({ start: range[0], end: range[range.length - 1] });
|
||||
} else {
|
||||
// For month view, range is an object with start and end
|
||||
setVisibleRange(range);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentView === 'agenda') {
|
||||
const filtered = evts?.filter(event => event.type === "assignment");
|
||||
setDisplayedEvents(filtered);
|
||||
} else {
|
||||
// Function to generate weekly occurrences of an event
|
||||
const recurringEvents = evts?.filter(event => event.type !== "assignment" && (event.dayOfMonth === null || event.dayOfMonth === undefined)) || [];
|
||||
const occurrences = recurringEvents?.flatMap(event => generateOccurrences(event, visibleRange.start, visibleRange.end)) || [];
|
||||
const nonRecurringEvents = evts?.filter(event => event.dayOfMonth !== null) || [];
|
||||
|
||||
setDisplayedEvents([...nonRecurringEvents, ...recurringEvents, ...occurrences]);
|
||||
}
|
||||
//setDisplayedEvents(evts);
|
||||
}, [visibleRange, evts, currentView]);
|
||||
|
||||
const handlers = useSwipeable({
|
||||
onSwipedLeft: () => navigate('NEXT'),
|
||||
onSwipedRight: () => navigate('PREV'),
|
||||
preventDefaultTouchmoveEvent: true,
|
||||
trackMouse: true,
|
||||
});
|
||||
const navigate = (action) => {
|
||||
console.log('navigate', action);
|
||||
setDate((currentDate) => {
|
||||
const newDate = new Date(currentDate);
|
||||
if (action === 'NEXT') {
|
||||
newDate.setMonth(newDate.getMonth() + 1);
|
||||
} else if (action === 'PREV') {
|
||||
newDate.setMonth(newDate.getMonth() - 1);
|
||||
}
|
||||
return newDate;
|
||||
});
|
||||
};
|
||||
|
||||
const generateOccurrences = (event, start, end) => {
|
||||
const occurrences = [];
|
||||
const eventStart = new Date(event.startTime);
|
||||
let current = new Date(event.startTime); // Assuming startTime has the start date
|
||||
|
||||
// Determine the end date for the event series
|
||||
const seriesEndDate = event.endDate ? new Date(event.endDate) : end;
|
||||
seriesEndDate.setHours(23, 59, 59); // Set to the end of the day
|
||||
|
||||
while (current <= seriesEndDate && current <= end) {
|
||||
// Check if the event should be repeated weekly or on a specific day of the month
|
||||
if (event.repeatWeekly && current.getDay() === eventStart.getDay()) {
|
||||
// For weekly recurring events
|
||||
addOccurrence(event, current, occurrences);
|
||||
} else if (event.dayOfMonth && current.getDate() === event.dayOfMonth) {
|
||||
// For specific day of month events
|
||||
addOccurrence(event, current, occurrences);
|
||||
}
|
||||
|
||||
// Move to the next day
|
||||
current = new Date(current.setDate(current.getDate() + 1));
|
||||
}
|
||||
|
||||
return occurrences;
|
||||
};
|
||||
|
||||
// Helper function to add an occurrence
|
||||
const addOccurrence = (event, current, occurrences) => {
|
||||
// Skip the original event date
|
||||
|
||||
const eventStart = new Date(event.startTime);
|
||||
const eventEnd = new Date(event.endTime);
|
||||
if (current.toDateString() !== eventStart.toDateString()) {
|
||||
const occurrence = {
|
||||
...event,
|
||||
startTime: new Date(current.setHours(eventStart.getHours(), eventStart.getMinutes())),
|
||||
endTime: new Date(current.setHours(eventEnd.getHours(), eventEnd.getMinutes())),
|
||||
date: current,
|
||||
type: 'recurring'
|
||||
};
|
||||
occurrences.push(occurrence);
|
||||
}
|
||||
};
|
||||
|
||||
// Define min and max times
|
||||
const minHour = 8; // 8:00 AM
|
||||
const maxHour = 20; // 8:00 PM
|
||||
const minTime = new Date();
|
||||
minTime.setHours(minHour, 0, 0);
|
||||
const maxTime = new Date();
|
||||
maxTime.setHours(maxHour, 0, 0);
|
||||
const totalHours = maxHour - minHour;
|
||||
|
||||
const handleSelect = ({ start, end }) => {
|
||||
if (!start || !end) return;
|
||||
if (start < new Date() || end < new Date() || start > end) return;
|
||||
|
||||
// Check if start and end are on the same day
|
||||
if (start.toDateString() !== end.toDateString()) {
|
||||
end = common.setTimeHHmm(start, "23:59");
|
||||
}
|
||||
|
||||
const startMinutes = common.getTimeInMinutes(start);
|
||||
const endMinutes = common.getTimeInMinutes(end);
|
||||
|
||||
// Adjust start and end times to be within min and max hours
|
||||
if (startMinutes < common.getTimeInMinutes(common.setTimeHHmm(start, minHour))) {
|
||||
start = common.setTimeHHmm(start, minHour);
|
||||
}
|
||||
if (endMinutes > common.getTimeInMinutes(common.setTimeHHmm(end, maxHour))) {
|
||||
end = common.setTimeHHmm(end, maxHour);
|
||||
}
|
||||
|
||||
setDate(start);
|
||||
|
||||
// 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,
|
||||
|
||||
});
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleEventClick = (event) => {
|
||||
if (event.type === "assignment") return;
|
||||
// Handle event click
|
||||
const eventForEditing = {
|
||||
...event,
|
||||
startTime: new Date(event.startTime),
|
||||
endTime: new Date(event.endTime),
|
||||
publisherId: event.publisherId || event.publisher?.connect?.id,
|
||||
repeatWeekly: event.repeatWeekly || false,
|
||||
};
|
||||
//strip title, start, end and allDay properties
|
||||
delete eventForEditing.title;
|
||||
delete eventForEditing.start;
|
||||
delete eventForEditing.end;
|
||||
delete eventForEditing.type;
|
||||
delete eventForEditing.publisher
|
||||
console.log("handleEventClick: " + eventForEditing);
|
||||
setSelectedEvent(eventForEditing);
|
||||
setIsModalOpen(true);
|
||||
|
||||
};
|
||||
|
||||
const handleDialogClose = async (dialogEvent) => {
|
||||
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 events = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisherId}`);
|
||||
var newEvents = events.data;
|
||||
setEvents(newEvents);
|
||||
|
||||
}
|
||||
|
||||
console.log("handleSave: ", dialogEvent);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
const EventWrapper = ({ event, style }) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
let eventStyle = {
|
||||
...style
|
||||
};
|
||||
const handleMouseEnter = () => setIsHovered(true);
|
||||
const handleMouseLeave = () => setIsHovered(false);
|
||||
if (currentView !== 'agenda') {
|
||||
//if event.type is availability show in blue. if it is schedule - green if confirmed, yellow if not confirmed
|
||||
//if event is not active - show in gray
|
||||
let bgColorClass = 'bg-gray-500'; // Default color for inactive events
|
||||
var bgColor = event.isactive ? "" : "bg-gray-500";
|
||||
if (event.type === "assignment") {
|
||||
bgColor = event.isConfirmed ? "bg-green-500" : "bg-yellow-500";
|
||||
//event.title = event.publisher.name; //ToDo: add other publishers names
|
||||
//event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime);
|
||||
} else {
|
||||
if (event.start !== undefined && event.end !== undefined && event.startTime !== null && event.endTime !== null) {
|
||||
try {
|
||||
if (event.type === "recurring") {
|
||||
//bgColor = "bg-blue-300";
|
||||
event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime);
|
||||
}
|
||||
else {
|
||||
event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
event.title = event.startTime + " - " + event.endTime;
|
||||
console.log("Error in EventWrapper: " + err);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
eventStyle = {
|
||||
...style,
|
||||
// backgroundColor: bgColorClass,
|
||||
//height: "50px",
|
||||
//color: 'white',
|
||||
whiteSpace: 'normal', // Allow the text to wrap to the next line
|
||||
overflow: 'hidden', // Hide overflowed content
|
||||
textOverflow: 'ellipsis' // Add ellipsis to text that's too long to fit
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
const onDelete = (event) => {
|
||||
// Remove the event from the calendar
|
||||
setEvents(currentEvents => currentEvents.filter(e => e.id !== event.id));
|
||||
};
|
||||
|
||||
const onConfirm = (event) => {
|
||||
console.log("onConfirm: " + event.id);
|
||||
toast.info("Вие потвърдихте!", { autoClose: 2000 });
|
||||
// Update the event data
|
||||
event.isConfirmed = true;
|
||||
event.isactive = false;
|
||||
// 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 !== event.id);
|
||||
return [...filteredEvents, event];
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div style={eventStyle} className={bgColor + " relative"}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave} >
|
||||
{event.title}
|
||||
{isHovered && event.type == "assignment" && (event.status == "pending" || event.status == undefined)
|
||||
&& (
|
||||
<div className="absolute top-1 left-0 right-0 flex justify-between px-1">
|
||||
{/* Delete Icon */}
|
||||
{/* <span
|
||||
className="disabled cursor-pointer rounded-full bg-red-500 text-white flex items-center justify-center"
|
||||
style={{ width: '24px', height: '24px' }} // Adjust the size as needed
|
||||
onClick={() => onDelete(event)}
|
||||
>
|
||||
✕
|
||||
</span> */}
|
||||
|
||||
{/* Confirm Icon */}
|
||||
{!event.isConfirmed && (
|
||||
<span
|
||||
className=" cursor-pointer rounded-full bg-green-500 text-white flex items-center justify-center"
|
||||
style={{ width: '24px', height: '24px' }} // Adjust the size as needed
|
||||
onClick={() => onConfirm(event)}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const eventStyleGetter = (event, start, end, isSelected) => {
|
||||
|
||||
//console.log("eventStyleGetter: " + event);
|
||||
let backgroundColor = '#3174ad'; // default color for calendar events - #3174ad
|
||||
if (currentView === 'agenda') {
|
||||
return { style: {} }
|
||||
}
|
||||
if (event.type === "assignment") {
|
||||
//event.title = event.publisher.name; //ToDo: add other publishers names
|
||||
}
|
||||
if (event.type === "availability") {
|
||||
|
||||
}
|
||||
if (event.isFromPreviousAssignment) { //ToDo: does it work?
|
||||
// orange-500 from Tailwind CSS
|
||||
backgroundColor = '#f56565';
|
||||
}
|
||||
if (event.isactive) {
|
||||
switch (event.type) {
|
||||
case 'assignment':
|
||||
backgroundColor = event.isConfirmed ? '#48bb78' : '#f6e05e'; // green-500 and yellow-300 from Tailwind CSS
|
||||
break;
|
||||
case 'recurring':
|
||||
backgroundColor = '#63b3ed'; // blue-300 from Tailwind CSS
|
||||
break;
|
||||
default: // availability
|
||||
//backgroundColor = '#a0aec0'; // gray-400 from Tailwind CSS
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
backgroundColor = '#a0aec0'; // Default color for inactive events
|
||||
}
|
||||
|
||||
return {
|
||||
style: {
|
||||
backgroundColor,
|
||||
opacity: 0.8,
|
||||
color: 'white',
|
||||
border: '0px',
|
||||
display: 'block',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Custom Toolbar Component
|
||||
|
||||
const CustomToolbar = ({ onNavigate, label, onView, view }) => {
|
||||
return (
|
||||
<div className="rbc-toolbar">
|
||||
<span className="rbc-btn-group">
|
||||
<button type="button" onClick={() => onNavigate('PREV')}>
|
||||
<FaArrowLeft className="icon-large" />
|
||||
</button>
|
||||
<button type="button" onClick={() => onNavigate('TODAY')}>
|
||||
<MdToday className="icon-large" />
|
||||
</button>
|
||||
<button type="button" onClick={() => onNavigate('NEXT')}>
|
||||
<FaArrowRight className="icon-large" />
|
||||
</button>
|
||||
</span>
|
||||
<span className="rbc-toolbar-label">{label}</span>
|
||||
<span className="rbc-btn-group">
|
||||
<button type="button" onClick={() => onView('month')} className={view === 'month' ? 'rbc-active' : ''}>
|
||||
<FaRegCalendarAlt className="icon-large" />
|
||||
</button>
|
||||
<button type="button" onClick={() => onView('week')} className={view === 'week' ? 'rbc-active' : ''}>
|
||||
<FaRegListAlt className="icon-large" />
|
||||
</button>
|
||||
<button type="button" onClick={() => onView('agenda')} className={view === 'agenda' ? 'rbc-active' : ''}>
|
||||
<FaRegCalendarCheck className="icon-large" />
|
||||
</button>
|
||||
{/* Add more view buttons as needed */}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<> <div {...handlers} className="flex flex-col"
|
||||
>
|
||||
{/* достъпности на {publisherId} */}
|
||||
<ToastContainer position="top-center" style={{ zIndex: 9999 }} />
|
||||
</div>
|
||||
<Calendar
|
||||
localizer={localizer}
|
||||
events={displayedEvents}
|
||||
startAccessor="startTime"
|
||||
endAccessor="endTime"
|
||||
selectable={true}
|
||||
onSelectSlot={handleSelect}
|
||||
onSelectEvent={handleEventClick}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
min={minTime} // Set minimum time
|
||||
max={maxTime} // Set maximum time
|
||||
messages={messages}
|
||||
view={currentView}
|
||||
views={['month', 'week', 'agenda']}
|
||||
onView={view => setCurrentView(view)}
|
||||
onRangeChange={onRangeChange}
|
||||
components={{
|
||||
event: EventWrapper,
|
||||
toolbar: CustomToolbar,
|
||||
// ... other custom components
|
||||
}}
|
||||
eventPropGetter={(eventStyleGetter)}
|
||||
date={date}
|
||||
onNavigate={setDate}
|
||||
className="rounded-lg shadow-lg"
|
||||
/>
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
<div className="modal-content">
|
||||
<AvailabilityForm
|
||||
publisherId={publisherId}
|
||||
existingItem={selectedEvent}
|
||||
onDone={handleDialogClose}
|
||||
inline={true}
|
||||
// Pass other props as needed
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed inset-0 bg-black opacity-50" onClick={handleCancel}></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvCalendar;
|
Reference in New Issue
Block a user