All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 50s
2307 lines
96 KiB
JavaScript
2307 lines
96 KiB
JavaScript
import React, {useState, useEffect, useRef, useCallback} from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { AuthService, EventsService, CustomerService, ResourceService, VehicleService, EmployeeService } from "../../services";
|
|
import moment from 'moment';
|
|
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown, Spinner } from "react-bootstrap";
|
|
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
|
|
import {
|
|
viewMonthGrid,
|
|
createViewDay,
|
|
createViewWeek,
|
|
createViewMonthGrid
|
|
} from '@schedule-x/calendar';
|
|
import { createEventsServicePlugin } from '@schedule-x/events-service';
|
|
import { createEventModalPlugin} from '@schedule-x/event-modal';
|
|
import { createEventRecurrencePlugin } from "@schedule-x/event-recurrence";
|
|
import '@schedule-x/theme-default/dist/calendar.css';
|
|
import { Archive, PencilSquare, Filter, Plus, X } from "react-bootstrap-icons";
|
|
import DatePicker from "react-datepicker";
|
|
import { vehicleSlice } from "../../store";
|
|
import Select from 'react-select';
|
|
// import { Scheduler } from "@aldabil/react-scheduler";
|
|
|
|
|
|
const EventsCalendar = () => {
|
|
const navigate = useNavigate();
|
|
const calendarTabOrder = ['medicalCalendar', 'activitiesCalendar', 'incidentsCalendar', 'mealPlanCalendar', 'reminderDatesCalendar'];
|
|
const getFirstVisibleCalendarTab = () => {
|
|
return calendarTabOrder.find((tabKey) => AuthService.canViewCalendarTab(tabKey) || AuthService.canEditCalendarTab(tabKey)) || 'medicalCalendar';
|
|
};
|
|
const [events, setEvents] = useState([]);
|
|
const calendarColumnRef = useRef(null);
|
|
const [listHeight, setListHeight] = useState(null);
|
|
const [allEvents, setAllEvents] = useState([]);
|
|
const [targetedEventType, setTargetedEventType] = useState('medical');
|
|
const [currentTab, setCurrentTab] = useState(getFirstVisibleCalendarTab());
|
|
const currentTabRef = useRef(getFirstVisibleCalendarTab());
|
|
const [customers, setCustomers] = useState([]);
|
|
const [resources, setResources] = useState([]);
|
|
const [fromDate, setFromDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
|
const [toDate, setToDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0));
|
|
const [currentTotalTranslate1, setCurrentTotalTranslate1] = useState(0);
|
|
const [currentTotalTranslate2, setCurrentTotalTranslate2] = useState(0);
|
|
const [currentTotalResource, setCurrentTotalResource] = useState(0);
|
|
const [showDeletedItems, setShowDeletedItems] = useState(false);
|
|
const [selectedColorFilters, setSelectedColorFilters] = useState([]);
|
|
const [timeData, setTimeData] = useState([]);
|
|
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
|
|
const eventsServicePlugin = createEventsServicePlugin();
|
|
const eventModalService = createEventModalPlugin();
|
|
const eventRecurrence = createEventRecurrencePlugin();
|
|
const [groupedEvents, setGroupedEvents] = useState(new Map());
|
|
const [currentRangeStart, setCurrentRangeStart] = useState(null);
|
|
const [currentRangeEnd, setCurrentRangeEnd] = useState(null);
|
|
const [showCreationModal, setShowCreationModal] = useState(false);
|
|
const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
|
|
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date())
|
|
const [newEventType, setNewEventType] = useState('');
|
|
const [newEventTitle, setNewEventTitle] = useState('');
|
|
const [newEventDescription, setNewEventDescription] = useState('');
|
|
const [newEventLocation, setNewEventLocation] = useState('');
|
|
const [newEventTarget, setNewEventTarget] = useState(undefined);
|
|
const [newEventSource, setNewEventSource] = useState(undefined);
|
|
const [newEventDepartment, setNewEventDepartment] = useState('');
|
|
const [newEventColor, setNewEventColor] = useState('');
|
|
const [newEventSourceType, setNewEventSourceType] = useState('');
|
|
const [newEventTargetType, setNewEventTargetType] = useState('');
|
|
const [newEventFutureDate, setNewEventFutureDate] = useState(undefined);
|
|
const [newEventReminderType, setNewEventReminderType] = useState('');
|
|
const [newEventRecurring, setNewEventRecurring] = useState(undefined);
|
|
const [vehicles, setVehicles] = useState([]);
|
|
const [employees, setEmployees] = useState([]);
|
|
|
|
// Medical appointment specific fields
|
|
const [newEventCustomer, setNewEventCustomer] = useState('');
|
|
const [newEventResource, setNewEventResource] = useState('');
|
|
const [newEventInterpreter, setNewEventInterpreter] = useState('');
|
|
const [newEventFasting, setNewEventFasting] = useState('');
|
|
const [newEventNeedId, setNewEventNeedId] = useState('');
|
|
const [newEventNewPatient, setNewEventNewPatient] = useState('');
|
|
const [newEventDisability, setNewEventDisability] = useState('');
|
|
const [newEventTransMethod, setNewEventTransMethod] = useState('');
|
|
|
|
// Activity specific fields
|
|
const [newActivityCategory, setNewActivityCategory] = useState('');
|
|
|
|
// Attendance Note specific fields
|
|
const [newAttendanceCustomer, setNewAttendanceCustomer] = useState(null);
|
|
const [newAttendanceReason, setNewAttendanceReason] = useState('');
|
|
|
|
// Meal Plan specific fields
|
|
const [newMealType, setNewMealType] = useState('');
|
|
const [newMealIngredients, setNewMealIngredients] = useState('');
|
|
|
|
// Important Dates specific fields
|
|
const [newReminderTitleCategory, setNewReminderTitleCategory] = useState('');
|
|
const [newReminderAssociatedEntity, setNewReminderAssociatedEntity] = useState(null);
|
|
|
|
// Repeat date fields (new modal)
|
|
const [newRepeatStartDate, setNewRepeatStartDate] = useState(null);
|
|
const [newRepeatEndDate, setNewRepeatEndDate] = useState(null);
|
|
const [newIndefiniteRepeat, setNewIndefiniteRepeat] = useState(false);
|
|
|
|
// Event recurrences data
|
|
const [allEventRecurrences, setAllEventRecurrences] = useState([]);
|
|
|
|
// Edit modal state (for non-medical tabs)
|
|
const [showEditModal, setShowEditModal] = useState(false);
|
|
const [editingEventId, setEditingEventId] = useState(null);
|
|
const [editEventTitle, setEditEventTitle] = useState('');
|
|
const [editEventStartDateTime, setEditEventStartDateTime] = useState(null);
|
|
const [editEventLocation, setEditEventLocation] = useState('');
|
|
const [editEventRecurring, setEditEventRecurring] = useState('');
|
|
// Activity edit fields
|
|
const [editActivityCategory, setEditActivityCategory] = useState('');
|
|
// Attendance edit fields
|
|
const [editAttendanceCustomer, setEditAttendanceCustomer] = useState(null);
|
|
const [editAttendanceReason, setEditAttendanceReason] = useState('');
|
|
// Meal Plan edit fields
|
|
const [editMealType, setEditMealType] = useState('');
|
|
const [editMealIngredients, setEditMealIngredients] = useState('');
|
|
// Important Dates edit fields
|
|
const [editReminderTitleCategory, setEditReminderTitleCategory] = useState('');
|
|
const [editReminderAssociatedEntity, setEditReminderAssociatedEntity] = useState(null);
|
|
// Repeat date fields (edit modal)
|
|
const [editRepeatStartDate, setEditRepeatStartDate] = useState(null);
|
|
const [editRepeatEndDate, setEditRepeatEndDate] = useState(null);
|
|
const [editIndefiniteRepeat, setEditIndefiniteRepeat] = useState(false);
|
|
const [editingRecurId, setEditingRecurId] = useState(null); // tracks if editing a recurrence rule
|
|
// Delete confirmation modal
|
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
const [showSpinner, setShowSpinner] = useState(false);
|
|
const visibleCalendarTabs = calendarTabOrder.filter((tabKey) => AuthService.canViewCalendarTab(tabKey) || AuthService.canEditCalendarTab(tabKey));
|
|
const canEditCurrentTab = () => AuthService.canEditCalendarTab(currentTab);
|
|
|
|
// Helper function to format name from "lastname, firstname" to "firstname lastname"
|
|
const formatFullName = (name) => {
|
|
if (!name) return '';
|
|
if (name.includes(',')) {
|
|
const parts = name.split(',').map(part => part.trim());
|
|
return `${parts[1]} ${parts[0]}`; // firstname lastname
|
|
}
|
|
return name;
|
|
};
|
|
|
|
// Helper function to get shortened name
|
|
const getShortenedName = (name) => {
|
|
if (!name) return '';
|
|
const fullName = formatFullName(name);
|
|
const parts = fullName.split(' ');
|
|
if (parts.length >= 2) {
|
|
return `${parts[0]} ${parts[parts.length - 1].charAt(0)}`; // FirstName L
|
|
}
|
|
return fullName;
|
|
};
|
|
|
|
// Helper function to format event title - now uses full name, CSS handles overflow
|
|
const formatEventTitle = (customerName, startTime) => {
|
|
const fullName = formatFullName(customerName);
|
|
// Return full name - CSS will handle truncation with text-overflow: ellipsis
|
|
return fullName;
|
|
};
|
|
|
|
// Get full name for description/tooltip
|
|
const getEventDescription = (customerName, doctorName) => {
|
|
const fullName = formatFullName(customerName);
|
|
return doctorName ? `${fullName} - ${doctorName}` : fullName;
|
|
};
|
|
|
|
// Helper: expand a recurring rule into individual event instances for a date range
|
|
const expandRecurrence = (rule, rangeFrom, rangeTo) => {
|
|
const instances = [];
|
|
const startDate = new Date(rule.start_repeat_date);
|
|
const endDate = new Date(rule.end_repeat_date);
|
|
const from = new Date(rangeFrom);
|
|
const to = new Date(rangeTo);
|
|
|
|
// Determine the effective range (overlap of rule range and visible range)
|
|
const effectiveStart = from > startDate ? from : startDate;
|
|
const effectiveEnd = to < endDate ? to : endDate;
|
|
if (effectiveStart > effectiveEnd) return instances;
|
|
|
|
const freq = rule.rrule; // e.g. 'FREQ=DAILY', 'FREQ=WEEKLY', 'FREQ=MONTHLY', 'FREQ=YEARLY'
|
|
let current = new Date(startDate);
|
|
|
|
// Limit to 1000 instances max to avoid infinite loops
|
|
let count = 0;
|
|
while (current <= effectiveEnd && count < 1000) {
|
|
if (current >= effectiveStart) {
|
|
const dateStr = moment(current).format('YYYY-MM-DD');
|
|
instances.push({
|
|
...rule,
|
|
id: `recur-${rule.id}-${dateStr}`,
|
|
_recur_id: rule.id,
|
|
start_time: new Date(current),
|
|
stop_time: new Date(current),
|
|
});
|
|
}
|
|
// Advance to next occurrence
|
|
if (freq === 'FREQ=DAILY') {
|
|
current = new Date(current.getTime() + 24 * 60 * 60 * 1000);
|
|
} else if (freq === 'FREQ=WEEKLY') {
|
|
current = new Date(current.getTime() + 7 * 24 * 60 * 60 * 1000);
|
|
} else if (freq === 'FREQ=MONTHLY') {
|
|
const next = new Date(current);
|
|
next.setMonth(next.getMonth() + 1);
|
|
current = next;
|
|
} else if (freq === 'FREQ=YEARLY') {
|
|
const next = new Date(current);
|
|
next.setFullYear(next.getFullYear() + 1);
|
|
current = next;
|
|
} else {
|
|
break;
|
|
}
|
|
count++;
|
|
}
|
|
return instances;
|
|
};
|
|
|
|
const buildDynamicBirthdayEvents = (customerList = [], rangeFrom, rangeTo) => {
|
|
if (!rangeFrom || !rangeTo) return [];
|
|
const from = new Date(rangeFrom);
|
|
const to = new Date(rangeTo);
|
|
const startYear = from.getFullYear();
|
|
const endYear = to.getFullYear();
|
|
const dynamicEvents = [];
|
|
|
|
customerList.forEach((customer) => {
|
|
const birthDate = `${customer?.birth_date || ''}`;
|
|
const [month, day] = birthDate.split('/').map(Number);
|
|
if (!month || !day) return;
|
|
|
|
for (let year = startYear; year <= endYear; year++) {
|
|
const birthday = new Date(year, month - 1, day);
|
|
if (Number.isNaN(birthday.getTime())) continue;
|
|
if (birthday < from || birthday > to) continue;
|
|
dynamicEvents.push({
|
|
id: `dynamic-birthday-${customer.id}-${year}`,
|
|
type: 'reminder',
|
|
status: 'active',
|
|
title: `${customer?.name}'s Birthday`,
|
|
description: `Happy Birthday to ${customer?.name}`,
|
|
start_time: birthday,
|
|
stop_time: new Date(birthday.getTime() + 10 * 60 * 1000),
|
|
color: 'member_related',
|
|
target_type: 'customer',
|
|
target_uuid: customer?.id,
|
|
target_name: customer?.name,
|
|
event_reminder_type: 'birthday',
|
|
rrule: 'FREQ=YEARLY',
|
|
create_by: 'system-dynamic',
|
|
edit_by: 'system-dynamic'
|
|
});
|
|
}
|
|
});
|
|
|
|
return dynamicEvents;
|
|
};
|
|
|
|
const eventTypeMap = {
|
|
medicalCalendar: 'medical',
|
|
activitiesCalendar: 'activity',
|
|
incidentsCalendar: 'incident',
|
|
mealPlanCalendar: 'meal_plan',
|
|
reminderDatesCalendar: 'reminder'
|
|
}
|
|
|
|
const calendar = useCalendarApp({
|
|
views: [createViewMonthGrid(), createViewDay(), createViewWeek()],
|
|
monthGridOptions: {
|
|
/**
|
|
* Number of events to display in a day cell before the "+ N events" button is shown
|
|
* */
|
|
nEventsPerDay: 50,
|
|
},
|
|
defaultView: viewMonthGrid.name,
|
|
skipValidation: true,
|
|
selectedDate: moment(new Date()).format('YYYY-MM-DD HH:mm'),
|
|
events: events,
|
|
plugins: [eventModalService, eventsServicePlugin, eventRecurrence],
|
|
callbacks: {
|
|
onRangeUpdate(range) {
|
|
console.log('new calendar range start date', range.start);
|
|
console.log('new calendar range end date', range.end);
|
|
setCurrentRangeStart(range.start);
|
|
setCurrentRangeEnd(range.end);
|
|
// Update fromDate/toDate for API fetching based on the range
|
|
const startDate = new Date(range.start);
|
|
const endDate = new Date(range.end);
|
|
setFromDate(new Date(startDate.getFullYear(), startDate.getMonth(), 1));
|
|
setToDate(new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0));
|
|
},
|
|
onClickDate(date) {
|
|
if (currentTabRef.current === 'medicalCalendar' || !AuthService.canEditCalendarTab(currentTabRef.current)) return;
|
|
// Parse as local date to avoid UTC timezone offset shifting the day
|
|
const [y, m, d] = date.split('-').map(Number);
|
|
const localDate = new Date(y, m - 1, d);
|
|
// Default to 10:00 AM for Activities tab
|
|
if (currentTabRef.current === 'activitiesCalendar') {
|
|
localDate.setHours(10, 0, 0, 0);
|
|
}
|
|
setNewEventStartDateTime(localDate);
|
|
setNewEventEndDateTime(localDate);
|
|
setShowCreationModal(true);
|
|
},
|
|
onClickDateTime(dateTime) {
|
|
if (currentTabRef.current === 'medicalCalendar' || !AuthService.canEditCalendarTab(currentTabRef.current)) return;
|
|
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
|
|
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
|
|
setShowCreationModal(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Filter events based on current calendar range
|
|
const getFilteredEvents = () => {
|
|
console.log("CenterCalendar - range:", currentRangeStart, "to", currentRangeEnd, "events:", events.length);
|
|
|
|
if (!currentRangeStart || !currentRangeEnd) {
|
|
// If no range set yet, show all events for current month
|
|
const now = moment();
|
|
return events.filter(event => {
|
|
const eventDate = moment(event.start_time);
|
|
return eventDate.isSame(now, 'month');
|
|
});
|
|
}
|
|
|
|
// Filter events within the calendar's visible range
|
|
const rangeStart = moment(currentRangeStart);
|
|
const rangeEnd = moment(currentRangeEnd);
|
|
|
|
return events.filter(event => {
|
|
const eventDate = moment(event.start_time);
|
|
return eventDate.isBetween(rangeStart, rangeEnd, 'day', '[]');
|
|
});
|
|
};
|
|
|
|
const getGroupedEvents = () => {
|
|
const eventsDateMap = new Map();
|
|
const filteredEvents = getFilteredEvents();
|
|
const sortedFilteredEvents = [...filteredEvents].sort((a, b) => {
|
|
const aTime = moment(a?.start_time).valueOf();
|
|
const bTime = moment(b?.start_time).valueOf();
|
|
return aTime - bTime;
|
|
});
|
|
for (const eventItem of sortedFilteredEvents) {
|
|
const dateString = moment(eventItem.start_time).format('MMM Do, YYYY');
|
|
if (eventsDateMap.has(dateString)) {
|
|
eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]);
|
|
} else {
|
|
const value = [];
|
|
value.push(eventItem);
|
|
eventsDateMap.set(dateString, value);
|
|
}
|
|
}
|
|
// Sort each day by earliest start time first
|
|
eventsDateMap.forEach((items, key) => {
|
|
const sortedItems = [...items].sort((a, b) => {
|
|
const aTime = moment(a?.start_time).valueOf();
|
|
const bTime = moment(b?.start_time).valueOf();
|
|
return aTime - bTime;
|
|
});
|
|
eventsDateMap.set(key, sortedItems);
|
|
});
|
|
return new Map(
|
|
[...eventsDateMap.entries()].sort((a, b) => {
|
|
const aDate = moment(a[1]?.[0]?.start_time).valueOf();
|
|
const bDate = moment(b[1]?.[0]?.start_time).valueOf();
|
|
return aDate - bDate;
|
|
})
|
|
);
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
if (!AuthService.canViewCalendar()) {
|
|
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
|
|
AuthService.logout();
|
|
navigate(`/login`);
|
|
}
|
|
setShowSpinner(true);
|
|
Promise.all([
|
|
VehicleService.getAllActiveVehicles().then((data) => {
|
|
setVehicles(data.data)
|
|
}),
|
|
EmployeeService.getAllEmployees().then((data) => {
|
|
setEmployees(data.data);
|
|
}),
|
|
CustomerService.getAllActiveCustomers().then((data) => {
|
|
setCustomers(data.data);
|
|
}),
|
|
ResourceService.getAll().then((data) => {
|
|
setResources(data.data);
|
|
}),
|
|
EventsService.getTimeData().then(data => {
|
|
setTimeData(data.data);
|
|
})
|
|
]).finally(() => {
|
|
setShowSpinner(false);
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!visibleCalendarTabs.includes(currentTab)) {
|
|
const nextTab = getFirstVisibleCalendarTab();
|
|
setCurrentTab(nextTab);
|
|
currentTabRef.current = nextTab;
|
|
}
|
|
}, [currentTab]);
|
|
|
|
useEffect(() => {
|
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
|
|
EventsService.getAllEventRecurrences().then(data => setAllEventRecurrences(data?.data || []));
|
|
}, [fromDate, toDate]);
|
|
|
|
// Auto-fill repeat start date from event date when repeat option is selected (new modal)
|
|
useEffect(() => {
|
|
if (newEventRecurring && newEventStartDateTime) {
|
|
setNewRepeatStartDate(new Date(newEventStartDateTime));
|
|
}
|
|
if (!newEventRecurring) {
|
|
setNewRepeatStartDate(null);
|
|
setNewRepeatEndDate(null);
|
|
setNewIndefiniteRepeat(false);
|
|
}
|
|
}, [newEventRecurring, newEventStartDateTime]);
|
|
|
|
useEffect(() => {
|
|
setNewEventType(eventTypeMap[currentTab]);
|
|
if (currentTab === 'medicalCalendar') {
|
|
if (customers?.length > 0 && resources.length > 0) {
|
|
const orignialEvents = [...allEvents];
|
|
setEvents(orignialEvents?.filter(item => item.type === 'medical')?.map((item) => {
|
|
const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
|
|
const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
|
|
item.event_id = item.id;
|
|
item.customer = customerField;
|
|
item.doctor = doctorField;
|
|
item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
|
|
item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
|
|
item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
|
|
item.translation = item?.data?.interpreter || '';
|
|
item.newPatient = item?.data?.new_patient || '';
|
|
item.needId = item?.data?.need_id || '';
|
|
item.disability = item?.data?.disability || '';
|
|
item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
|
item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '' ;
|
|
item.fasting = item?.data?.fasting || '';
|
|
item.transportation = item?.link_event_name || '';
|
|
item.title = currentTab==='medicalCalendar' ? formatEventTitle(customerField, item?.start_time) : item.title;
|
|
item.description = currentTab==='medicalCalendar' ? getEventDescription(customerField, doctorField) : item.description; // Full info for tooltip
|
|
item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
|
|
item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
|
const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData);
|
|
const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
|
|
item.color = item?.color;
|
|
item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
|
item.showWarnings = isFutureEvent;
|
|
item.maxTranslate1 = maxTranslate1;
|
|
item.maxTranslate2 = maxTranslate2;
|
|
item.maxResource = maxResource;
|
|
item.totalTranslate1 = totalTranslate1;
|
|
setCurrentTotalTranslate1(item.totalTranslate1);
|
|
item.totalTranslate2 = totalTranslate2;
|
|
setCurrentTotalTranslate2(item.totalTranslate2);
|
|
item.totalResource = totalResource;
|
|
setCurrentTotalResource(item.totalResource);
|
|
return item;
|
|
})?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems)
|
|
?.filter(item => {
|
|
if (selectedColorFilters.length === 0) return true;
|
|
if (selectedColorFilters.includes(item.color)) return true;
|
|
// When "Drop-Off Only" (purple) is selected, also show events with no label
|
|
if (selectedColorFilters.includes('purple') && !item.color) return true;
|
|
return false;
|
|
}));
|
|
}
|
|
} else {
|
|
// Expand recurring event rules into individual instances and merge with regular events
|
|
let recurInstances = allEventRecurrences
|
|
.filter(rule => rule.type === eventTypeMap[currentTab] && rule.status === 'active')
|
|
.flatMap(rule => expandRecurrence(rule, fromDate, toDate));
|
|
let baseEvents = [...allEvents];
|
|
if (currentTab === 'reminderDatesCalendar') {
|
|
// Birthday reminders are rendered dynamically from customer data.
|
|
baseEvents = baseEvents.filter((item) => item?.event_reminder_type !== 'birthday');
|
|
recurInstances = recurInstances.filter((item) => item?.event_reminder_type !== 'birthday');
|
|
}
|
|
const originalEvents = [...baseEvents, ...recurInstances];
|
|
const dynamicBirthdayEvents = currentTab === 'reminderDatesCalendar'
|
|
? buildDynamicBirthdayEvents(customers, fromDate, toDate)
|
|
: [];
|
|
const mergedEvents = [...originalEvents, ...dynamicBirthdayEvents];
|
|
let filteredEvents = mergedEvents?.filter(item => item.type === eventTypeMap[currentTab])?.map(item => {
|
|
// For Important Dates, remap old blue/orange colors to new member_related/vehicle_maintenance
|
|
let eventColor = item?.color;
|
|
if (currentTab === 'reminderDatesCalendar') {
|
|
const isMember = ['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(item?.event_reminder_type) || item?.target_type === 'customer';
|
|
eventColor = isMember ? 'member_related' : 'vehicle_maintenance';
|
|
}
|
|
const additionalClasses = [`event-${eventColor || 'primary'}`];
|
|
// Disable clicking for birthday events on Important Dates tab
|
|
if (currentTab === 'reminderDatesCalendar' && item?.event_reminder_type === 'birthday') {
|
|
additionalClasses.push('event-no-click');
|
|
}
|
|
// For Important Dates, use date-only format so tiles don't show hour:minute
|
|
const dateFormat = (currentTab === 'reminderDatesCalendar' || currentTab === 'incidentsCalendar') ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm';
|
|
return {
|
|
...item,
|
|
color: eventColor,
|
|
title: item?.title,
|
|
start: item?.start_time? `${moment(new Date(item?.start_time)).format(dateFormat)}` : `${moment().format(dateFormat)}`,
|
|
end: item?.stop_time? `${moment(new Date(item?.stop_time)).format(dateFormat)}` : (item?.start_time? `${moment(item?.start_time).format(dateFormat)}` : `${moment().format(dateFormat)}`),
|
|
_options: { additionalClasses }
|
|
};
|
|
})?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems)
|
|
?.filter(item => {
|
|
if (selectedColorFilters.length === 0) return true;
|
|
// For Important Dates, filter by event_reminder_type
|
|
if (currentTab === 'reminderDatesCalendar') {
|
|
return selectedColorFilters.includes(item.event_reminder_type);
|
|
}
|
|
if (selectedColorFilters.includes(item.color)) return true;
|
|
// When "Drop-Off Only" (purple) is selected, also show events with no label
|
|
if (selectedColorFilters.includes('purple') && !item.color) return true;
|
|
return false;
|
|
});
|
|
|
|
// For Important Dates tab, filter to only show events for active customers
|
|
if (currentTab === 'reminderDatesCalendar' && customers?.length > 0) {
|
|
filteredEvents = filteredEvents?.filter(item => {
|
|
// If it's a customer-related event, check if the customer is active
|
|
if (item.target_type === 'customer' && item.target_uuid) {
|
|
const customer = customers.find(c => c.id === item.target_uuid);
|
|
// Only show if customer exists and is active (not discharged/transferred/deceased)
|
|
return customer && customer.status === 'active' &&
|
|
customer.type !== 'discharged' &&
|
|
customer.type !== 'transferred' &&
|
|
customer.type !== 'deceased';
|
|
}
|
|
// Vehicle-related events are always shown
|
|
return true;
|
|
});
|
|
}
|
|
|
|
setEvents(filteredEvents);
|
|
}
|
|
}, [customers, resources, timeData, currentTab, allEvents, allEventRecurrences, showDeletedItems, selectedColorFilters])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
if (events && calendar) {
|
|
console.log("CenterCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd);
|
|
calendar?.eventsService?.set(events);
|
|
setGroupedEvents(getGroupedEvents());
|
|
}
|
|
}, [events, currentRangeStart, currentRangeEnd]);
|
|
|
|
// Fallback close handler: ensure ScheduleX modal can always be dismissed via X button.
|
|
useEffect(() => {
|
|
const onDocumentClickCapture = (event) => {
|
|
const target = event.target;
|
|
if (!(target instanceof HTMLElement)) return;
|
|
if (!target.closest('.sx__event-modal')) return;
|
|
const closeControl = target.closest('button,[role="button"]');
|
|
if (!closeControl) return;
|
|
const closeAriaLabel = `${closeControl.getAttribute('aria-label') || ''}`.toLowerCase();
|
|
const closeText = `${closeControl.textContent || ''}`.trim().toLowerCase();
|
|
const isCloseControl = closeAriaLabel.includes('close') || closeText === '\u00d7' || closeText === 'x' || closeText === '\u2715';
|
|
if (!isCloseControl) return;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
eventModalService?.close?.();
|
|
try {
|
|
calendar?.config?.plugins?.eventModal?.close();
|
|
} catch (_) {
|
|
// no-op fallback
|
|
}
|
|
setTimeout(() => {
|
|
document.querySelectorAll('.sx__event-modal').forEach(el => el.remove());
|
|
}, 0);
|
|
};
|
|
document.addEventListener('click', onDocumentClickCapture, true);
|
|
return () => {
|
|
document.removeEventListener('click', onDocumentClickCapture, true);
|
|
};
|
|
}, [calendar, eventModalService]);
|
|
|
|
// Sync list column height with calendar column height
|
|
useEffect(() => {
|
|
const updateListHeight = () => {
|
|
if (calendarColumnRef.current) {
|
|
const calendarHeight = calendarColumnRef.current.offsetHeight;
|
|
setListHeight(calendarHeight);
|
|
}
|
|
};
|
|
|
|
// Initial measurement after render
|
|
const timer = setTimeout(updateListHeight, 100);
|
|
|
|
// Update on window resize
|
|
window.addEventListener('resize', updateListHeight);
|
|
|
|
return () => {
|
|
clearTimeout(timer);
|
|
window.removeEventListener('resize', updateListHeight);
|
|
};
|
|
}, [events, currentTab]);
|
|
|
|
|
|
const redirectToAdmin = () => {
|
|
navigate(`/medical`)
|
|
}
|
|
|
|
const goToEdit = (id) => {
|
|
if (!AuthService.canEditMedicalEvents()) return;
|
|
navigate(`/medical/events/edit/${id}?from=calendar`)
|
|
}
|
|
|
|
const goToCreateNew = () => {
|
|
if (!AuthService.canEditMedicalEvents()) return;
|
|
navigate(`/medical/events`)
|
|
}
|
|
|
|
const goToList = () => {
|
|
navigate(`/medical/events/list`)
|
|
}
|
|
|
|
const goToMultipleList = () => {
|
|
navigate(`/medical/events/multiple-list`)
|
|
}
|
|
|
|
|
|
const goToView = (id) => {
|
|
navigate(`/medical/events/${id}`)
|
|
}
|
|
|
|
const disableEvent = (id) => {
|
|
if (currentTab === 'medicalCalendar') {
|
|
if (!AuthService.canEditMedicalEvents()) return;
|
|
} else {
|
|
if (!AuthService.canEditCalendarTab(currentTab)) return;
|
|
}
|
|
// Handle recurring event instances
|
|
const isRecurInstance = typeof id === 'string' && id.startsWith('recur-');
|
|
if (isRecurInstance) {
|
|
const currentEvent = events.find(item => item.id === id);
|
|
const recurId = currentEvent?._recur_id || id.split('-')[1];
|
|
EventsService.disableEventRecurrence(recurId, { status: 'inactive' }).then(() => {
|
|
Promise.all([
|
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }),
|
|
EventsService.getAllEventRecurrences()
|
|
]).then(([eventsRes, recurRes]) => {
|
|
setAllEvents(eventsRes.data);
|
|
setAllEventRecurrences(recurRes.data || []);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
const currentEvent = events.find(item => item.id === id);
|
|
EventsService.disableEvent(id, { status: 'inactive', edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
|
|
edit_date: new Date(),
|
|
edit_history: currentEvent?.edit_history? [...currentEvent.edit_history, { employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] : [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]}).then(() => {
|
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
|
setAllEvents(data?.data);
|
|
// if (currentTab === 'medicalCalendar') {
|
|
// setEvents(data.data.filter((item) => {
|
|
// const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || '');
|
|
// const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || '');
|
|
// item.event_id = item.id;
|
|
// item.customer = customerField;
|
|
// item.doctor = doctorField;
|
|
// item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || '');
|
|
// item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '')
|
|
// item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.resource_address || '') : (item?.data?.resource_address || '');
|
|
// item.translation = item?.data?.interpreter || '';
|
|
// item.newPatient = item?.data?.new_patient || '';
|
|
// item.needId = item?.data?.need_id || '';
|
|
// item.disability = item?.data?.disability || '';
|
|
// item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}` ;
|
|
// item.endTime = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
|
// item.fasting = item?.data?.fasting || '';
|
|
// item.transportation = item?.link_event_name || '';
|
|
// item.title = `${customerField}, provider: ${doctorField}`;
|
|
// item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`;
|
|
// item.end = item.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`);
|
|
// item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]};
|
|
// const transportationInfo = EventsService.getTransportationInfo(data.data, item, timeData);
|
|
// const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo;
|
|
// item.color = item?.color;
|
|
// item.showWarnings = isFutureEvent;
|
|
// item.maxTranslate1 = maxTranslate1;
|
|
// item.maxTranslate2 = maxTranslate2;
|
|
// item.maxResource = maxResource;
|
|
// item.totalTranslate1 = totalTranslate1;
|
|
// setCurrentTotalTranslate1(item.totalTranslate1);
|
|
// item.totalTranslate2 = totalTranslate2;
|
|
// setCurrentTotalTranslate2(item.totalTranslate2);
|
|
// item.totalResource = totalResource;
|
|
// setCurrentTotalResource(item.totalResource);
|
|
// return item;
|
|
// }).filter(item => item.type === 'medical'));
|
|
// }
|
|
})
|
|
});
|
|
}
|
|
|
|
// Edit modal functions (for non-medical tabs)
|
|
const openEditModal = (calendarEvent) => {
|
|
if (!AuthService.canEditCalendarTab(currentTab)) return;
|
|
// Check if this is a recurring event instance (ID starts with 'recur-')
|
|
const isRecurInstance = typeof calendarEvent.id === 'string' && calendarEvent.id.startsWith('recur-');
|
|
let eventData;
|
|
|
|
if (isRecurInstance) {
|
|
// Find the recurrence rule using the _recur_id stored on expanded instances
|
|
const recurId = calendarEvent._recur_id || calendarEvent.id.split('-')[1];
|
|
const recurRule = allEventRecurrences.find(r => r.id === recurId);
|
|
if (recurRule) {
|
|
eventData = { ...recurRule };
|
|
setEditingRecurId(recurId);
|
|
setEditingEventId(null);
|
|
setEditRepeatStartDate(recurRule.start_repeat_date ? new Date(recurRule.start_repeat_date) : null);
|
|
setEditRepeatEndDate(recurRule.end_repeat_date ? new Date(recurRule.end_repeat_date) : null);
|
|
setEditIndefiniteRepeat(recurRule.indefinite_repeat || false);
|
|
} else {
|
|
return; // Can't find rule, bail
|
|
}
|
|
} else {
|
|
eventData = allEvents.find(e => e.id === calendarEvent.id) || calendarEvent;
|
|
setEditingEventId(eventData.id);
|
|
setEditingRecurId(null);
|
|
setEditRepeatStartDate(null);
|
|
setEditRepeatEndDate(null);
|
|
setEditIndefiniteRepeat(false);
|
|
}
|
|
|
|
setEditEventStartDateTime(eventData.start_time ? new Date(eventData.start_time) : (eventData.start_repeat_date ? new Date(eventData.start_repeat_date) : new Date()));
|
|
|
|
if (currentTab === 'activitiesCalendar') {
|
|
setEditEventTitle(eventData.title || '');
|
|
setEditActivityCategory(eventData.activity_category || eventData.color || '');
|
|
setEditEventLocation(eventData.event_location || '');
|
|
setEditEventRecurring(eventData.rrule || '');
|
|
} else if (currentTab === 'incidentsCalendar') {
|
|
setEditAttendanceCustomer(eventData.target_uuid ? { value: eventData.target_uuid, label: eventData.target_name } : null);
|
|
setEditAttendanceReason(eventData.description || '');
|
|
setEditEventRecurring(eventData.rrule || '');
|
|
} else if (currentTab === 'mealPlanCalendar') {
|
|
setEditEventTitle(eventData.title || '');
|
|
setEditMealType(eventData.meal_type || '');
|
|
setEditMealIngredients(eventData.ingredients || '');
|
|
setEditEventRecurring(eventData.rrule || '');
|
|
} else if (currentTab === 'reminderDatesCalendar') {
|
|
setEditReminderTitleCategory(eventData.event_reminder_type || '');
|
|
setEditReminderAssociatedEntity(eventData.target_uuid ? { value: eventData.target_uuid, label: eventData.target_name } : null);
|
|
setEditEventRecurring(eventData.rrule || '');
|
|
}
|
|
|
|
// If editing a regular event that has an rrule, populate repeat date fields
|
|
if (!isRecurInstance && eventData.rrule) {
|
|
setEditRepeatStartDate(eventData.start_time ? new Date(eventData.start_time) : null);
|
|
setEditRepeatEndDate(null);
|
|
setEditIndefiniteRepeat(false);
|
|
}
|
|
|
|
setShowEditModal(true);
|
|
};
|
|
|
|
const closeEditModal = () => {
|
|
setShowEditModal(false);
|
|
setEditingEventId(null);
|
|
setEditingRecurId(null);
|
|
setEditEventTitle('');
|
|
setEditEventStartDateTime(null);
|
|
setEditEventLocation('');
|
|
setEditEventRecurring('');
|
|
setEditActivityCategory('');
|
|
setEditAttendanceCustomer(null);
|
|
setEditAttendanceReason('');
|
|
setEditMealType('');
|
|
setEditMealIngredients('');
|
|
setEditReminderTitleCategory('');
|
|
setEditReminderAssociatedEntity(null);
|
|
setEditRepeatStartDate(null);
|
|
setEditRepeatEndDate(null);
|
|
setEditIndefiniteRepeat(false);
|
|
};
|
|
|
|
const handleEditSave = () => {
|
|
if (!AuthService.canEditCalendarTab(currentTab)) return;
|
|
const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name;
|
|
|
|
let data = {
|
|
edit_by: userName,
|
|
edit_date: new Date(),
|
|
};
|
|
|
|
if (currentTab === 'activitiesCalendar') {
|
|
data = {
|
|
...data,
|
|
title: editEventTitle,
|
|
start_time: editEventStartDateTime,
|
|
stop_time: editEventStartDateTime,
|
|
activity_category: editActivityCategory,
|
|
color: editActivityCategory,
|
|
event_location: editEventLocation,
|
|
};
|
|
} else if (currentTab === 'incidentsCalendar') {
|
|
data = {
|
|
...data,
|
|
title: editAttendanceCustomer?.label ? `Attendance Note - ${editAttendanceCustomer.label}` : 'Attendance Note',
|
|
start_time: editEventStartDateTime,
|
|
stop_time: editEventStartDateTime,
|
|
target_type: 'customer',
|
|
target_uuid: editAttendanceCustomer?.value,
|
|
target_name: editAttendanceCustomer?.label,
|
|
description: editAttendanceReason,
|
|
};
|
|
} else if (currentTab === 'mealPlanCalendar') {
|
|
data = {
|
|
...data,
|
|
title: editEventTitle,
|
|
start_time: editEventStartDateTime,
|
|
stop_time: editEventStartDateTime,
|
|
meal_type: editMealType,
|
|
color: editMealType === 'breakfast' ? 'brown' : (editMealType === 'lunch' ? 'green' : 'red'),
|
|
ingredients: editMealIngredients,
|
|
};
|
|
} else if (currentTab === 'reminderDatesCalendar') {
|
|
const isMemberCategory = ['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory);
|
|
data = {
|
|
...data,
|
|
title: getReminderTitleLabel(editReminderTitleCategory),
|
|
start_time: editEventStartDateTime,
|
|
stop_time: editEventStartDateTime,
|
|
event_reminder_type: editReminderTitleCategory,
|
|
target_type: isMemberCategory ? 'customer' : 'vehicle',
|
|
target_uuid: editReminderAssociatedEntity?.value,
|
|
target_name: editReminderAssociatedEntity?.label,
|
|
color: isMemberCategory ? 'member_related' : 'vehicle_maintenance',
|
|
};
|
|
}
|
|
|
|
const refreshAll = () => {
|
|
Promise.all([
|
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }),
|
|
EventsService.getAllEventRecurrences()
|
|
]).then(([eventsRes, recurRes]) => {
|
|
setAllEvents(eventsRes.data);
|
|
setAllEventRecurrences(recurRes.data || []);
|
|
closeEditModal();
|
|
});
|
|
};
|
|
|
|
// If editing a recurrence rule
|
|
if (editingRecurId) {
|
|
const recurData = {
|
|
...data,
|
|
rrule: editEventRecurring,
|
|
start_repeat_date: editRepeatStartDate,
|
|
end_repeat_date: editIndefiniteRepeat ? new Date('2099-12-31') : editRepeatEndDate,
|
|
indefinite_repeat: editIndefiniteRepeat,
|
|
};
|
|
delete recurData.start_time;
|
|
delete recurData.stop_time;
|
|
|
|
if (!editEventRecurring) {
|
|
// Changed from recurring to one-time: delete recurrence rule, create regular event
|
|
EventsService.deleteEventRecurrence(editingRecurId).then(() => {
|
|
const eventData = { ...data };
|
|
delete eventData.rrule;
|
|
eventData.start_time = editEventStartDateTime;
|
|
eventData.stop_time = editEventStartDateTime;
|
|
eventData.type = eventTypeMap[currentTab];
|
|
eventData.status = 'active';
|
|
EventsService.createNewEvent(eventData).then(() => refreshAll());
|
|
});
|
|
} else {
|
|
EventsService.updateEventRecurrence(editingRecurId, recurData).then(() => refreshAll());
|
|
}
|
|
} else if (editingEventId) {
|
|
// Editing a regular event
|
|
if (editEventRecurring) {
|
|
// Changed from one-time to recurring: create recurrence rule, delete regular event
|
|
const recurData = {
|
|
...data,
|
|
type: eventTypeMap[currentTab],
|
|
status: 'active',
|
|
rrule: editEventRecurring,
|
|
start_repeat_date: editRepeatStartDate || editEventStartDateTime,
|
|
end_repeat_date: editIndefiniteRepeat ? new Date('2099-12-31') : editRepeatEndDate,
|
|
indefinite_repeat: editIndefiniteRepeat,
|
|
create_by: data.edit_by,
|
|
create_date: new Date(),
|
|
};
|
|
delete recurData.start_time;
|
|
delete recurData.stop_time;
|
|
EventsService.createEventRecurrence(recurData).then(() => {
|
|
EventsService.disableEvent(editingEventId, {}).then(() => refreshAll());
|
|
});
|
|
} else {
|
|
// Still one-time, just update normally
|
|
delete data.rrule;
|
|
EventsService.updateEvent(editingEventId, data).then(() => refreshAll());
|
|
}
|
|
}
|
|
};
|
|
|
|
const toggleColorFilter = (colorValue) => {
|
|
setSelectedColorFilters(prev =>
|
|
prev.includes(colorValue)
|
|
? prev.filter(c => c !== colorValue)
|
|
: [...prev, colorValue]
|
|
);
|
|
};
|
|
|
|
const FilterAndClose = () => {
|
|
setShowFilterDropdown(false);
|
|
}
|
|
|
|
const cleanFilterAndClose = () => {
|
|
setShowFilterDropdown(false);
|
|
setShowDeletedItems(false);
|
|
setSelectedColorFilters([]);
|
|
}
|
|
|
|
const goToTab = (value) => {
|
|
if (!value || !visibleCalendarTabs.includes(value)) return;
|
|
setTargetedEventType(eventTypeMap[value]);
|
|
setCurrentTab(value);
|
|
currentTabRef.current = value;
|
|
// Always hide deleted events by default when switching tabs.
|
|
setShowDeletedItems(false);
|
|
setSelectedColorFilters([]);
|
|
// Dismiss any open calendar event tile popup via plugin API
|
|
try {
|
|
calendar?.config?.plugins?.eventModal?.close();
|
|
} catch (e) {
|
|
// Fallback: remove the modal element from DOM
|
|
document.querySelectorAll('.sx__event-modal').forEach(el => el.remove());
|
|
}
|
|
}
|
|
|
|
const dismissEventModal = (e) => {
|
|
e?.preventDefault?.();
|
|
e?.stopPropagation?.();
|
|
try {
|
|
eventModalService?.close?.();
|
|
} catch (_e) {}
|
|
try {
|
|
calendar?.config?.plugins?.eventModal?.close();
|
|
} catch (e) {
|
|
document.querySelectorAll('.sx__event-modal').forEach(el => el.remove());
|
|
}
|
|
};
|
|
|
|
|
|
const customComponents = {
|
|
eventModal: ({calendarEvent}) => {
|
|
return <>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '8px' }}>
|
|
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</div>
|
|
<button
|
|
type="button"
|
|
onClick={dismissEventModal}
|
|
aria-label="Close"
|
|
title="Close"
|
|
style={{
|
|
border: 'none',
|
|
background: 'transparent',
|
|
padding: '8px',
|
|
marginTop: '4px',
|
|
marginRight: '4px',
|
|
cursor: 'pointer',
|
|
lineHeight: 1,
|
|
color: '#666',
|
|
minWidth: '36px',
|
|
minHeight: '36px',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center'
|
|
}}
|
|
>
|
|
<X size={24} />
|
|
</button>
|
|
</div>
|
|
{calendarEvent?.doctor && <div className="sx__event-modal__time">{`${calendarEvent?.doctor}`}</div>}
|
|
<div className="sx__event-modal__time">{`${calendarEvent?.start}`}</div>
|
|
<div className="sx__event-modal__time" style={{ display: 'flex', gap: '12px', marginTop: '8px' }}>
|
|
{((currentTab === 'medicalCalendar' && AuthService.canEditMedicalEvents()) || (currentTab !== 'medicalCalendar' && AuthService.canEditCalendarTab(currentTab))) && <PencilSquare
|
|
size={16}
|
|
onClick={() => currentTab === 'medicalCalendar' ? goToEdit(calendarEvent?.id) : openEditModal(calendarEvent)}
|
|
style={{ cursor: 'pointer' }}
|
|
title="Edit"
|
|
/>}
|
|
{((currentTab === 'medicalCalendar' && AuthService.canEditMedicalEvents()) || (currentTab !== 'medicalCalendar' && AuthService.canEditCalendarTab(currentTab))) && <Archive
|
|
size={16}
|
|
onClick={() => { setDeleteTargetId(calendarEvent?.id); setShowDeleteConfirm(true); }}
|
|
style={{ cursor: 'pointer' }}
|
|
title="Delete"
|
|
/>}
|
|
</div>
|
|
</>
|
|
}
|
|
};
|
|
|
|
const customMenu = React.forwardRef(
|
|
({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
style={style}
|
|
className={className}
|
|
aria-labelledby={labeledBy}
|
|
>
|
|
<h6>Filter By</h6>
|
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
<div className="field-label" style={{ marginBottom: '8px' }}>Filter by Type</div>
|
|
{currentTab === 'reminderDatesCalendar' ? (
|
|
<>
|
|
<div style={{ fontSize: '11px', fontWeight: '600', color: '#0066B1', marginBottom: '4px', marginTop: '4px' }}>Member-Related</div>
|
|
{EventsService.importantDatesFilterOptions.filter(o => o.group === 'member_related').map((item) => (
|
|
<div key={item.value} className="d-flex align-items-center mb-1" style={{ cursor: 'pointer', paddingLeft: '4px' }} onClick={() => toggleColorFilter(item.value)}>
|
|
<input
|
|
type="checkbox"
|
|
checked={selectedColorFilters.includes(item.value)}
|
|
onChange={() => toggleColorFilter(item.value)}
|
|
style={{ marginRight: '8px', marginLeft: '0' }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
<span
|
|
className="event-member_related"
|
|
style={{
|
|
width: '14px',
|
|
height: '14px',
|
|
borderRadius: '3px',
|
|
display: 'inline-block',
|
|
marginRight: '6px',
|
|
flexShrink: 0
|
|
}}
|
|
></span>
|
|
<span style={{ fontSize: '12px' }}>{item.label}</span>
|
|
</div>
|
|
))}
|
|
<div style={{ fontSize: '11px', fontWeight: '600', color: '#9C2A10', marginBottom: '4px', marginTop: '8px' }}>Vehicle Maintenance</div>
|
|
{EventsService.importantDatesFilterOptions.filter(o => o.group === 'vehicle_maintenance').map((item) => (
|
|
<div key={item.value} className="d-flex align-items-center mb-1" style={{ cursor: 'pointer', paddingLeft: '4px' }} onClick={() => toggleColorFilter(item.value)}>
|
|
<input
|
|
type="checkbox"
|
|
checked={selectedColorFilters.includes(item.value)}
|
|
onChange={() => toggleColorFilter(item.value)}
|
|
style={{ marginRight: '8px', marginLeft: '0' }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
<span
|
|
className="event-vehicle_maintenance"
|
|
style={{
|
|
width: '14px',
|
|
height: '14px',
|
|
borderRadius: '3px',
|
|
display: 'inline-block',
|
|
marginRight: '6px',
|
|
flexShrink: 0
|
|
}}
|
|
></span>
|
|
<span style={{ fontSize: '12px' }}>{item.label}</span>
|
|
</div>
|
|
))}
|
|
</>
|
|
) : (
|
|
getLegendOptions().map((item) => (
|
|
<div key={item.value} className="d-flex align-items-center mb-1" style={{ cursor: 'pointer' }} onClick={() => toggleColorFilter(item.value)}>
|
|
<input
|
|
type="checkbox"
|
|
checked={selectedColorFilters.includes(item.value)}
|
|
onChange={() => toggleColorFilter(item.value)}
|
|
style={{ marginRight: '8px', marginLeft: '0' }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
<span
|
|
className={`event-${item.value}`}
|
|
style={{
|
|
width: '14px',
|
|
height: '14px',
|
|
borderRadius: '3px',
|
|
display: 'inline-block',
|
|
marginRight: '6px',
|
|
flexShrink: 0
|
|
}}
|
|
></span>
|
|
<span style={{ fontSize: '12px' }}>{item.label}</span>
|
|
</div>
|
|
))
|
|
)}
|
|
{selectedColorFilters.length > 0 && (
|
|
<div style={{ marginTop: '4px' }}>
|
|
<span
|
|
style={{ fontSize: '11px', color: '#0066B1', cursor: 'pointer', textDecoration: 'underline' }}
|
|
onClick={() => setSelectedColorFilters([])}
|
|
>
|
|
Clear all
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="list row" style={{ marginTop: '8px' }}>
|
|
<div className="col-md-12">
|
|
<button className="btn btn-default btn-sm float-right" onClick={() => cleanFilterAndClose()}> Cancel </button>
|
|
<button className="btn btn-primary btn-sm float-right" onClick={() => FilterAndClose()}> Filter </button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
// Get legend options based on current tab
|
|
const getLegendOptions = () => {
|
|
switch (currentTab) {
|
|
case 'medicalCalendar':
|
|
return EventsService.labelOptions;
|
|
case 'activitiesCalendar':
|
|
return EventsService.activityColorOptions;
|
|
case 'incidentsCalendar':
|
|
return EventsService.incidentColorOptions;
|
|
case 'mealPlanCalendar':
|
|
return EventsService.mealPlanColorOptions;
|
|
case 'reminderDatesCalendar':
|
|
return EventsService.importantDatesLegendOptions;
|
|
default:
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const calendarView = <div className="multi-columns-container" style={{ display: 'flex', alignItems: 'flex-start', width: '100%' }}>
|
|
<div className="column-container" ref={calendarColumnRef} style={{ minWidth: '1000px', flexShrink: 0, display: 'flex', flexDirection: 'column' }}>
|
|
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
|
|
{/* Dynamic Legend */}
|
|
<div className="calendar-legend mt-3">
|
|
<h6 className="text-muted mb-2" style={{ fontSize: '12px' }}>Legend:</h6>
|
|
<div className="d-flex flex-wrap gap-3">
|
|
{getLegendOptions().map((item) => (
|
|
<div key={item.value} className="d-flex align-items-center">
|
|
<span
|
|
className={`event-${item.value}`}
|
|
style={{
|
|
width: '16px',
|
|
height: '16px',
|
|
borderRadius: '4px',
|
|
display: 'inline-block',
|
|
marginRight: '6px'
|
|
}}
|
|
></span>
|
|
<span style={{ fontSize: '12px' }}>{item.label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="column-container calendar-list-column" style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: '320px', flexShrink: 0 }}>
|
|
<div className="column-card" style={{ height: listHeight ? `${listHeight}px` : 'auto', overflowY: 'auto', overflowX: 'auto', padding: '16px', display: 'flex', flexDirection: 'column', width: '100%', minWidth: '320px', boxSizing: 'border-box' }}>
|
|
<h6 className="text-primary mb-3">List</h6>
|
|
<div style={{ flex: 1, overflowY: 'auto', overflowX: 'auto', width: '100%' }}>
|
|
{(!groupedEvents || groupedEvents.size === 0) && (
|
|
<div style={{ padding: '24px', textAlign: 'center', color: '#999' }}>
|
|
No events for this period
|
|
</div>
|
|
)}
|
|
{
|
|
Array.from(groupedEvents?.keys())?.map((key) => {
|
|
return <div key={key}>
|
|
<h6 className="text-primary me-2">{key}</h6>
|
|
{
|
|
groupedEvents.get(key).map(eventItem => <div
|
|
key={eventItem.id}
|
|
className={`event-${eventItem.color || 'primary'} mb-3 event-list-item-container`}
|
|
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
|
|
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default', padding: '8px 12px', borderRadius: '4px' }}
|
|
>
|
|
{/* Medical Events */}
|
|
{currentTab === 'medicalCalendar' && (
|
|
<>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span className="sx__month-agenda-event__title">{formatFullName(eventItem.customer)}</span>
|
|
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('HH:mm')} - {moment(eventItem?.stop_time || eventItem?.start_time).format('HH:mm')}</span>
|
|
</div>
|
|
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Provider: {eventItem?.doctor || '-'}</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Activities */}
|
|
{currentTab === 'activitiesCalendar' && (
|
|
<>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span className="sx__month-agenda-event__title">{eventItem.title}</span>
|
|
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('HH:mm')}</span>
|
|
</div>
|
|
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Location: {eventItem?.event_location || '-'}</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Attendance Notes */}
|
|
{currentTab === 'incidentsCalendar' && (
|
|
<>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span className="sx__month-agenda-event__title">{eventItem?.target_name || eventItem.title}</span>
|
|
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('MM/DD')}</span>
|
|
</div>
|
|
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Reason: {eventItem?.description || '-'}</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Meal Plan */}
|
|
{currentTab === 'mealPlanCalendar' && (
|
|
<>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span className="sx__month-agenda-event__title">{eventItem.title}</span>
|
|
<span style={{ fontSize: '12px', textTransform: 'capitalize' }}>{eventItem?.meal_type || '-'}</span>
|
|
</div>
|
|
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>Ingredients: {eventItem?.ingredients || '-'}</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Important Dates / Reminders */}
|
|
{currentTab === 'reminderDatesCalendar' && (
|
|
<>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span className="sx__month-agenda-event__title">{eventItem.title}</span>
|
|
<span style={{ fontSize: '12px' }}>{moment(eventItem?.start_time).format('MM/DD')}</span>
|
|
</div>
|
|
<div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '4px' }}>{eventItem?.target_type === 'vehicle' ? 'Vehicle' : 'Person'}: {eventItem?.target_name || '-'}</div>
|
|
{eventItem?.event_prediction_date && <div className="sx__event-modal__time" style={{ fontSize: '12px', marginTop: '2px' }}>Deadline: {moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}</div>}
|
|
</>
|
|
)}
|
|
</div>)
|
|
}
|
|
</div>
|
|
})
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
const handleClose = () => {
|
|
setNewEventDescription('');
|
|
setNewEventTitle('');
|
|
setNewEventLocation('');
|
|
setNewEventSource(undefined);
|
|
setNewEventTarget(undefined);
|
|
setNewEventStartDateTime(undefined);
|
|
setNewEventType('');
|
|
setNewEventFutureDate(undefined);
|
|
setNewEventSourceType('');
|
|
setNewEventTargetType('');
|
|
setShowCreationModal(false);
|
|
setNewEventEndDateTime(undefined);
|
|
setNewEventColor('');
|
|
setNewEventRecurring(undefined);
|
|
setNewRepeatStartDate(null);
|
|
setNewRepeatEndDate(null);
|
|
setNewIndefiniteRepeat(false);
|
|
setNewEventReminderType('');
|
|
// Reset medical fields
|
|
setNewEventCustomer('');
|
|
setNewEventResource('');
|
|
setNewEventInterpreter('');
|
|
setNewEventFasting('');
|
|
setNewEventNeedId('');
|
|
setNewEventNewPatient('');
|
|
setNewEventDisability('');
|
|
setNewEventTransMethod('');
|
|
// Reset activity fields
|
|
setNewActivityCategory('');
|
|
// Reset attendance note fields
|
|
setNewAttendanceCustomer(null);
|
|
setNewAttendanceReason('');
|
|
// Reset meal plan fields
|
|
setNewMealType('');
|
|
setNewMealIngredients('');
|
|
// Reset important dates fields
|
|
setNewReminderTitleCategory('');
|
|
setNewReminderAssociatedEntity(null);
|
|
}
|
|
const handleSave = () => {
|
|
if ((currentTab === 'medicalCalendar' && !AuthService.canEditMedicalEvents()) || (currentTab !== 'medicalCalendar' && !AuthService.canEditCalendarTab(currentTab))) return;
|
|
const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name;
|
|
|
|
let data = {
|
|
type: newEventType,
|
|
status: 'active',
|
|
create_by: userName,
|
|
edit_by: userName,
|
|
edit_date: new Date(),
|
|
create_date: new Date(),
|
|
edit_history: [{ employee: userName, date: new Date() }]
|
|
};
|
|
|
|
// Handle Medical Appointments
|
|
if (currentTab === 'medicalCalendar') {
|
|
const selectedCustomer = customers.find(c => c.id === newEventCustomer);
|
|
const selectedResource = resources.find(r => r.id === newEventResource);
|
|
data = {
|
|
...data,
|
|
title: selectedCustomer ? `${selectedCustomer.name} - Medical Appointment` : 'Medical Appointment',
|
|
start_time: newEventStartDateTime,
|
|
stop_time: newEventStartDateTime,
|
|
color: newEventColor,
|
|
confirmed: true,
|
|
data: {
|
|
customer: newEventCustomer,
|
|
client_name: selectedCustomer?.name || '',
|
|
resource: newEventResource,
|
|
resource_name: selectedResource?.name || '',
|
|
resource_phone: selectedResource?.phone || '',
|
|
resource_address: selectedResource?.address || '',
|
|
interpreter: newEventInterpreter,
|
|
fasting: newEventFasting,
|
|
need_id: newEventNeedId,
|
|
new_patient: newEventNewPatient,
|
|
disability: newEventDisability,
|
|
trans_method: newEventTransMethod,
|
|
}
|
|
};
|
|
}
|
|
|
|
// Handle Activities
|
|
if (currentTab === 'activitiesCalendar') {
|
|
data = {
|
|
...data,
|
|
title: newEventTitle,
|
|
start_time: newEventStartDateTime,
|
|
stop_time: newEventStartDateTime,
|
|
activity_category: newActivityCategory,
|
|
color: newActivityCategory, // category maps to color for display
|
|
event_location: newEventLocation,
|
|
};
|
|
}
|
|
|
|
// Handle Attendance Notes (Incidents)
|
|
if (currentTab === 'incidentsCalendar') {
|
|
data = {
|
|
...data,
|
|
title: newAttendanceCustomer?.label ? `Attendance Note - ${newAttendanceCustomer.label}` : 'Attendance Note',
|
|
start_time: newEventStartDateTime,
|
|
stop_time: newEventStartDateTime,
|
|
target_type: 'customer',
|
|
target_uuid: newAttendanceCustomer?.value,
|
|
target_name: newAttendanceCustomer?.label,
|
|
description: newAttendanceReason,
|
|
color: 'blue', // Default color for attendance notes
|
|
};
|
|
}
|
|
|
|
// Handle Meal Plan
|
|
if (currentTab === 'mealPlanCalendar') {
|
|
data = {
|
|
...data,
|
|
title: newEventTitle,
|
|
start_time: newEventStartDateTime,
|
|
stop_time: newEventStartDateTime,
|
|
meal_type: newMealType,
|
|
color: newMealType === 'breakfast' ? 'brown' : (newMealType === 'lunch' ? 'green' : 'red'),
|
|
ingredients: newMealIngredients,
|
|
};
|
|
}
|
|
|
|
// Handle Important Dates (Reminders)
|
|
if (currentTab === 'reminderDatesCalendar') {
|
|
const isMemberCategory = ['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory);
|
|
data = {
|
|
...data,
|
|
title: getReminderTitleLabel(newReminderTitleCategory),
|
|
start_time: newEventStartDateTime,
|
|
stop_time: newEventStartDateTime,
|
|
event_reminder_type: newReminderTitleCategory,
|
|
target_type: isMemberCategory ? 'customer' : 'vehicle',
|
|
target_uuid: newReminderAssociatedEntity?.value,
|
|
target_name: newReminderAssociatedEntity?.label,
|
|
color: isMemberCategory ? 'member_related' : 'vehicle_maintenance',
|
|
};
|
|
}
|
|
|
|
// If a repeat option is selected, save to calendar_event_recur collection
|
|
if (newEventRecurring && currentTab !== 'medicalCalendar') {
|
|
const recurData = {
|
|
...data,
|
|
rrule: newEventRecurring,
|
|
start_repeat_date: newRepeatStartDate || newEventStartDateTime,
|
|
end_repeat_date: newIndefiniteRepeat ? new Date('2099-12-31') : newRepeatEndDate,
|
|
indefinite_repeat: newIndefiniteRepeat,
|
|
};
|
|
// Remove rrule from main event data — recurring logic goes in calendar_event_recur only
|
|
delete recurData.start_time;
|
|
delete recurData.stop_time;
|
|
EventsService.createEventRecurrence(recurData).then(() => {
|
|
Promise.all([
|
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }),
|
|
EventsService.getAllEventRecurrences()
|
|
]).then(([eventsRes, recurRes]) => {
|
|
setAllEvents(eventsRes.data);
|
|
setAllEventRecurrences(recurRes.data || []);
|
|
handleClose();
|
|
});
|
|
});
|
|
} else {
|
|
// One-time event — save to calendar_event as before (strip rrule)
|
|
delete data.rrule;
|
|
EventsService.createNewEvent(data).then(() => {
|
|
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
|
|
setAllEvents(data.data);
|
|
handleClose();
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
// Helper function to get reminder title label
|
|
const getReminderTitleLabel = (value) => {
|
|
const memberOptions = {
|
|
'birthday': 'Birthday',
|
|
'adcaps_completion': 'ADCAPS Completion',
|
|
'center_qualification_expiration': 'Center Qualification Expiration'
|
|
};
|
|
const vehicleOptions = {
|
|
'oil_change': 'Oil Change',
|
|
'monthly_vehicle_inspection': 'Monthly Vehicle Inspection',
|
|
'emissions_inspection': 'Emissions Inspection',
|
|
'insurance_expiration': 'Insurance Expiration',
|
|
'license_plate_expiration': 'License Plate Expiration'
|
|
};
|
|
return memberOptions[value] || vehicleOptions[value] || value;
|
|
};
|
|
|
|
|
|
return (
|
|
<>
|
|
{showSpinner && <div className="spinner-overlay">
|
|
<Spinner animation="border" role="status">
|
|
<span className="visually-hidden">Loading...</span>
|
|
</Spinner>
|
|
</div>}
|
|
<div className="list row mb-4">
|
|
<Breadcrumb>
|
|
<Breadcrumb.Item href="/">General</Breadcrumb.Item>
|
|
<Breadcrumb.Item active>
|
|
Calendar
|
|
</Breadcrumb.Item>
|
|
</Breadcrumb>
|
|
<div className="col-md-12 text-primary">
|
|
<h4>
|
|
Calendar
|
|
</h4>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="app-main-content-list-container" style={{"min-width": "1500px"}}>
|
|
<div className="app-main-content-list-func-container">
|
|
<Tabs activeKey={currentTab} id="medical-calendar-tab" onSelect={(k) => goToTab(k)}>
|
|
{visibleCalendarTabs.includes('medicalCalendar') && <Tab eventKey="medicalCalendar" title="Medical Appointments">
|
|
|
|
</Tab>}
|
|
{visibleCalendarTabs.includes('activitiesCalendar') && <Tab eventKey="activitiesCalendar" title="Activities">
|
|
|
|
</Tab>}
|
|
{visibleCalendarTabs.includes('incidentsCalendar') && <Tab eventKey="incidentsCalendar" title="Attendance">
|
|
|
|
</Tab>}
|
|
{visibleCalendarTabs.includes('mealPlanCalendar') && <Tab eventKey="mealPlanCalendar" title="Meal Plan">
|
|
|
|
</Tab>}
|
|
{visibleCalendarTabs.includes('reminderDatesCalendar') && <Tab eventKey="reminderDatesCalendar" title="Important Dates">
|
|
|
|
</Tab>}
|
|
</Tabs>
|
|
{ calendarView}
|
|
<div className="list-func-panel">
|
|
{currentTab !== 'medicalCalendar' && canEditCurrentTab() && (
|
|
<button className="btn btn-primary me-2" onClick={() => {
|
|
setNewEventStartDateTime(new Date());
|
|
setNewEventEndDateTime(new Date());
|
|
setShowCreationModal(true);
|
|
}}>
|
|
<Plus size={18} className="me-1" />
|
|
{currentTab === 'activitiesCalendar' && 'Add New Activity'}
|
|
{currentTab === 'incidentsCalendar' && 'Add New Attendance Note'}
|
|
{currentTab === 'mealPlanCalendar' && 'Add New Meal Plan'}
|
|
{currentTab === 'reminderDatesCalendar' && 'Add New Important Date'}
|
|
</button>
|
|
)}
|
|
<Dropdown
|
|
key={'event-calendar-filter'}
|
|
id="event-calendar-filter"
|
|
show={showFilterDropdown}
|
|
onToggle={() => setShowFilterDropdown(!showFilterDropdown)}
|
|
autoClose={false}
|
|
>
|
|
<Dropdown.Toggle variant="primary">
|
|
<Filter size={16} className="me-2"></Filter>Filter
|
|
</Dropdown.Toggle>
|
|
<Dropdown.Menu as={customMenu}/>
|
|
</Dropdown>
|
|
</div>
|
|
<Modal show={showCreationModal} onHide={handleClose} size="sm" centered dialogClassName="calendar-event-modal">
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>
|
|
{currentTab === 'medicalCalendar' && 'New Medical Appointment'}
|
|
{currentTab === 'activitiesCalendar' && 'New Activity'}
|
|
{currentTab === 'incidentsCalendar' && 'New Attendance Note'}
|
|
{currentTab === 'mealPlanCalendar' && 'New Meal Item'}
|
|
{currentTab === 'reminderDatesCalendar' && 'New Important Date'}
|
|
</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
{/* Medical Appointment Fields */}
|
|
{currentTab === 'medicalCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Customer<span className="required">*</span></div>
|
|
<select className="form-control" value={newEventCustomer} onChange={(e) => setNewEventCustomer(e.target.value)}>
|
|
<option value="">Select Customer</option>
|
|
{customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Provider<span className="required">*</span></div>
|
|
<select className="form-control" value={newEventResource} onChange={(e) => setNewEventResource(e.target.value)}>
|
|
<option value="">Select Provider</option>
|
|
{resources.filter(item => item?.status === 'active').map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Appointment Time<span className="required">*</span></div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newEventStartDateTime}
|
|
onChange={setNewEventStartDateTime}
|
|
showTimeSelect
|
|
timeFormat="HH:mm"
|
|
timeIntervals={15}
|
|
dateFormat="MM/dd/yyyy, HH:mm"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Language Support</div>
|
|
<select className="form-control" value={newEventInterpreter} onChange={(e) => setNewEventInterpreter(e.target.value)}>
|
|
<option value="">Select</option>
|
|
<option value="Checkin">Checkin</option>
|
|
<option value="Center">Center</option>
|
|
<option value="Family">Family</option>
|
|
<option value="Office in person">Office in person</option>
|
|
<option value="Office by phone">Office by phone</option>
|
|
<option value="Nurse">Nurse</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Transportation Support</div>
|
|
<select className="form-control" value={newEventTransMethod} onChange={(e) => setNewEventTransMethod(e.target.value)}>
|
|
<option value="">Select</option>
|
|
<option value="ByOwn">Self-Transport</option>
|
|
<option value="By Center Transportation">By Center Transportation</option>
|
|
<option value="DropOff Only">Drop-Off Only</option>
|
|
<option value="Pickup Only">Pick-Up Only</option>
|
|
<option value="Client Does Not need to Go">Medication Pickup Only</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Label</div>
|
|
<select className="form-control" value={newEventColor} onChange={(e) => setNewEventColor(e.target.value)}>
|
|
<option value="">Select</option>
|
|
{EventsService.labelOptions?.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
|
</select>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Activity Fields */}
|
|
{currentTab === 'activitiesCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Activity Name<span className="required">*</span></div>
|
|
<input type="text" className="form-control" placeholder="Enter activity name" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Date & Time</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newEventStartDateTime}
|
|
onChange={setNewEventStartDateTime}
|
|
showTimeSelect
|
|
timeFormat="HH:mm"
|
|
timeIntervals={15}
|
|
dateFormat="MM/dd/yyyy, HH:mm"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Category<span className="required">*</span></div>
|
|
<select className="form-control" value={newActivityCategory} onChange={(e) => setNewActivityCategory(e.target.value)}>
|
|
<option value="">Select Category</option>
|
|
<option value="red">Classes</option>
|
|
<option value="pink">Games</option>
|
|
<option value="green">Events</option>
|
|
<option value="blue">Outings</option>
|
|
<option value="purple">Personal Care</option>
|
|
<option value="brown">Care Activities</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Location</div>
|
|
<input type="text" className="form-control" placeholder="Enter location" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat</div>
|
|
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
<option value="FREQ=YEARLY">Yearly</option>
|
|
</select>
|
|
</div>
|
|
{newEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatStartDate}
|
|
onChange={setNewRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatEndDate}
|
|
onChange={setNewRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={newIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={newIndefiniteRepeat} onChange={(e) => {
|
|
setNewIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setNewRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Attendance Note Fields */}
|
|
{currentTab === 'incidentsCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Customer Name<span className="required">*</span></div>
|
|
<Select
|
|
value={newAttendanceCustomer}
|
|
onChange={setNewAttendanceCustomer}
|
|
options={customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))}
|
|
placeholder="Select Customer"
|
|
isClearable
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Date<span className="required">*</span></div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newEventStartDateTime}
|
|
onChange={setNewEventStartDateTime}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Reason</div>
|
|
<input type="text" className="form-control" placeholder="Enter reason" value={newAttendanceReason || ''} onChange={e => setNewAttendanceReason(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat</div>
|
|
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
</select>
|
|
</div>
|
|
{newEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatStartDate}
|
|
onChange={setNewRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatEndDate}
|
|
onChange={setNewRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={newIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={newIndefiniteRepeat} onChange={(e) => {
|
|
setNewIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setNewRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Meal Item Fields */}
|
|
{currentTab === 'mealPlanCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Dish Name<span className="required">*</span></div>
|
|
<input type="text" className="form-control" placeholder="Enter dish name" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Meal Type<span className="required">*</span></div>
|
|
<select className="form-control" value={newMealType} onChange={(e) => setNewMealType(e.target.value)}>
|
|
<option value="">Select Meal Type</option>
|
|
<option value="breakfast">Breakfast</option>
|
|
<option value="lunch">Lunch</option>
|
|
<option value="snack">Snack</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newEventStartDateTime}
|
|
onChange={setNewEventStartDateTime}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat</div>
|
|
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
</select>
|
|
</div>
|
|
{newEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatStartDate}
|
|
onChange={setNewRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatEndDate}
|
|
onChange={setNewRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={newIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={newIndefiniteRepeat} onChange={(e) => {
|
|
setNewIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setNewRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
<div className="mb-3">
|
|
<div className="field-label">Ingredients</div>
|
|
<input type="text" className="form-control" placeholder="Enter ingredients" value={newMealIngredients || ''} onChange={e => setNewMealIngredients(e.target.value)}/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Important Dates Fields */}
|
|
{currentTab === 'reminderDatesCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Title<span className="required">*</span></div>
|
|
<select className="form-control" value={newReminderTitleCategory} onChange={(e) => {
|
|
setNewReminderTitleCategory(e.target.value);
|
|
setNewReminderAssociatedEntity(null); // Reset when category changes
|
|
}}>
|
|
<option value="">Select Title</option>
|
|
<optgroup label="Member">
|
|
<option value="adcaps_completion">ADCAPS Completion</option>
|
|
<option value="center_qualification_expiration">Center Qualification Expiration</option>
|
|
</optgroup>
|
|
<optgroup label="Vehicle">
|
|
<option value="oil_change">Oil Change</option>
|
|
<option value="monthly_vehicle_inspection">Monthly Vehicle Inspection</option>
|
|
<option value="emissions_inspection">Emissions Inspection</option>
|
|
<option value="insurance_expiration">Insurance Expiration</option>
|
|
<option value="license_plate_expiration">License Plate Expiration</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
{newReminderTitleCategory && (
|
|
<div className="mb-3">
|
|
<div className="field-label">
|
|
{['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory)
|
|
? 'Associated Person'
|
|
: 'Associated Vehicle'}<span className="required">*</span>
|
|
</div>
|
|
<Select
|
|
value={newReminderAssociatedEntity}
|
|
onChange={setNewReminderAssociatedEntity}
|
|
options={
|
|
['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory)
|
|
? customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))
|
|
: vehicles.map(v => ({ value: v.id, label: v.vehicle_number }))
|
|
}
|
|
placeholder={['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory) ? 'Select Person' : 'Select Vehicle'}
|
|
isClearable
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="mb-3">
|
|
<div className="field-label">Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newEventStartDateTime}
|
|
onChange={setNewEventStartDateTime}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Category</div>
|
|
<div className="field-value" style={{ padding: '8px 0', color: '#666' }}>
|
|
{newReminderTitleCategory
|
|
? (['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(newReminderTitleCategory) ? 'Member Related' : 'Vehicle Related')
|
|
: '-'}
|
|
</div>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat Cycle</div>
|
|
<select className="form-control" value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
<option value="FREQ=YEARLY">Yearly</option>
|
|
</select>
|
|
</div>
|
|
{newEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatStartDate}
|
|
onChange={setNewRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={newRepeatEndDate}
|
|
onChange={setNewRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={newIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={newIndefiniteRepeat} onChange={(e) => {
|
|
setNewIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setNewRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</Modal.Body>
|
|
<Modal.Footer>
|
|
<Button variant="secondary" size="sm" onClick={handleClose}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="primary" size="sm" onClick={handleSave} disabled={
|
|
(currentTab === 'medicalCalendar' && !AuthService.canEditMedicalEvents()) ||
|
|
(currentTab !== 'medicalCalendar' && !AuthService.canEditCalendarTab(currentTab)) ||
|
|
(currentTab === 'medicalCalendar' && (!newEventCustomer || !newEventResource || !newEventStartDateTime)) ||
|
|
(currentTab === 'activitiesCalendar' && (!newEventTitle || !newActivityCategory)) ||
|
|
(currentTab === 'incidentsCalendar' && (!newAttendanceCustomer || !newEventStartDateTime)) ||
|
|
(currentTab === 'mealPlanCalendar' && (!newEventTitle || !newMealType)) ||
|
|
(currentTab === 'reminderDatesCalendar' && (!newReminderTitleCategory || !newReminderAssociatedEntity))
|
|
}>
|
|
Save
|
|
</Button>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
|
|
{/* Edit Event Modal (non-medical tabs) */}
|
|
<Modal show={showEditModal} onHide={closeEditModal} size="sm" centered dialogClassName="calendar-event-modal">
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>
|
|
{currentTab === 'activitiesCalendar' && 'Edit Activity'}
|
|
{currentTab === 'incidentsCalendar' && 'Edit Attendance Note'}
|
|
{currentTab === 'mealPlanCalendar' && 'Edit Meal Item'}
|
|
{currentTab === 'reminderDatesCalendar' && 'Edit Important Date'}
|
|
</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
{/* Activity Edit Fields */}
|
|
{currentTab === 'activitiesCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Activity Name<span className="required">*</span></div>
|
|
<input type="text" className="form-control" placeholder="Enter activity name" value={editEventTitle || ''} onChange={e => setEditEventTitle(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Date & Time</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editEventStartDateTime}
|
|
onChange={setEditEventStartDateTime}
|
|
showTimeSelect
|
|
timeFormat="HH:mm"
|
|
timeIntervals={15}
|
|
dateFormat="MM/dd/yyyy, HH:mm"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Category<span className="required">*</span></div>
|
|
<select className="form-control" value={editActivityCategory} onChange={(e) => setEditActivityCategory(e.target.value)}>
|
|
<option value="">Select Category</option>
|
|
<option value="red">Classes</option>
|
|
<option value="pink">Games</option>
|
|
<option value="green">Events</option>
|
|
<option value="blue">Outings</option>
|
|
<option value="purple">Personal Care</option>
|
|
<option value="brown">Care Activities</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Location</div>
|
|
<input type="text" className="form-control" placeholder="Enter location" value={editEventLocation || ''} onChange={e => setEditEventLocation(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat</div>
|
|
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
<option value="FREQ=YEARLY">Yearly</option>
|
|
</select>
|
|
</div>
|
|
{editEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatStartDate}
|
|
onChange={setEditRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatEndDate}
|
|
onChange={setEditRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={editIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={editIndefiniteRepeat} onChange={(e) => {
|
|
setEditIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setEditRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Attendance Note Edit Fields */}
|
|
{currentTab === 'incidentsCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Customer Name<span className="required">*</span></div>
|
|
<Select
|
|
value={editAttendanceCustomer}
|
|
onChange={setEditAttendanceCustomer}
|
|
options={customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))}
|
|
placeholder="Select Customer"
|
|
isClearable
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Date<span className="required">*</span></div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editEventStartDateTime}
|
|
onChange={setEditEventStartDateTime}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Reason</div>
|
|
<input type="text" className="form-control" placeholder="Enter reason" value={editAttendanceReason || ''} onChange={e => setEditAttendanceReason(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat</div>
|
|
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
</select>
|
|
</div>
|
|
{editEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatStartDate}
|
|
onChange={setEditRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatEndDate}
|
|
onChange={setEditRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={editIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={editIndefiniteRepeat} onChange={(e) => {
|
|
setEditIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setEditRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Meal Item Edit Fields */}
|
|
{currentTab === 'mealPlanCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Dish Name<span className="required">*</span></div>
|
|
<input type="text" className="form-control" placeholder="Enter dish name" value={editEventTitle || ''} onChange={e => setEditEventTitle(e.target.value)}/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Meal Type<span className="required">*</span></div>
|
|
<select className="form-control" value={editMealType} onChange={(e) => setEditMealType(e.target.value)}>
|
|
<option value="">Select Meal Type</option>
|
|
<option value="breakfast">Breakfast</option>
|
|
<option value="lunch">Lunch</option>
|
|
<option value="snack">Snack</option>
|
|
</select>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editEventStartDateTime}
|
|
onChange={setEditEventStartDateTime}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat</div>
|
|
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
</select>
|
|
</div>
|
|
{editEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatStartDate}
|
|
onChange={setEditRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatEndDate}
|
|
onChange={setEditRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={editIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={editIndefiniteRepeat} onChange={(e) => {
|
|
setEditIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setEditRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
<div className="mb-3">
|
|
<div className="field-label">Ingredients</div>
|
|
<input type="text" className="form-control" placeholder="Enter ingredients" value={editMealIngredients || ''} onChange={e => setEditMealIngredients(e.target.value)}/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Important Dates Edit Fields */}
|
|
{currentTab === 'reminderDatesCalendar' && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Title<span className="required">*</span></div>
|
|
<select className="form-control" value={editReminderTitleCategory} onChange={(e) => {
|
|
setEditReminderTitleCategory(e.target.value);
|
|
setEditReminderAssociatedEntity(null);
|
|
}}>
|
|
<option value="">Select Title</option>
|
|
<optgroup label="Member">
|
|
<option value="adcaps_completion">ADCAPS Completion</option>
|
|
<option value="center_qualification_expiration">Center Qualification Expiration</option>
|
|
</optgroup>
|
|
<optgroup label="Vehicle">
|
|
<option value="oil_change">Oil Change</option>
|
|
<option value="monthly_vehicle_inspection">Monthly Vehicle Inspection</option>
|
|
<option value="emissions_inspection">Emissions Inspection</option>
|
|
<option value="insurance_expiration">Insurance Expiration</option>
|
|
<option value="license_plate_expiration">License Plate Expiration</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
{editReminderTitleCategory && (
|
|
<div className="mb-3">
|
|
<div className="field-label">
|
|
{['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory)
|
|
? 'Associated Person'
|
|
: 'Associated Vehicle'}<span className="required">*</span>
|
|
</div>
|
|
<Select
|
|
value={editReminderAssociatedEntity}
|
|
onChange={setEditReminderAssociatedEntity}
|
|
options={
|
|
['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory)
|
|
? customers.filter(item => item?.status === 'active' && item?.type !== 'discharged').map(c => ({ value: c.id, label: c.name }))
|
|
: vehicles.map(v => ({ value: v.id, label: v.vehicle_number }))
|
|
}
|
|
placeholder={['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory) ? 'Select Person' : 'Select Vehicle'}
|
|
isClearable
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="mb-3">
|
|
<div className="field-label">Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editEventStartDateTime}
|
|
onChange={setEditEventStartDateTime}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Category</div>
|
|
<div className="field-value" style={{ padding: '8px 0', color: '#666' }}>
|
|
{editReminderTitleCategory
|
|
? (['birthday', 'adcaps_completion', 'center_qualification_expiration'].includes(editReminderTitleCategory) ? 'Member Related' : 'Vehicle Related')
|
|
: '-'}
|
|
</div>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">Repeat Cycle</div>
|
|
<select className="form-control" value={editEventRecurring} onChange={(e) => setEditEventRecurring(e.target.value)}>
|
|
<option value="">No (One-time)</option>
|
|
<option value="FREQ=DAILY">Daily</option>
|
|
<option value="FREQ=WEEKLY">Weekly</option>
|
|
<option value="FREQ=MONTHLY">Monthly</option>
|
|
<option value="FREQ=YEARLY">Yearly</option>
|
|
</select>
|
|
</div>
|
|
{editEventRecurring && (
|
|
<>
|
|
<div className="mb-3">
|
|
<div className="field-label">Start Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatStartDate}
|
|
onChange={setEditRepeatStartDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<div className="field-label">End Repeat Date</div>
|
|
<DatePicker
|
|
className="form-control"
|
|
selected={editRepeatEndDate}
|
|
onChange={setEditRepeatEndDate}
|
|
dateFormat="MM/dd/yyyy"
|
|
disabled={editIndefiniteRepeat}
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={editIndefiniteRepeat} onChange={(e) => {
|
|
setEditIndefiniteRepeat(e.target.checked);
|
|
if (e.target.checked) setEditRepeatEndDate(new Date('2099-12-31'));
|
|
}} />
|
|
Indefinite Repeat
|
|
</label>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</Modal.Body>
|
|
<Modal.Footer>
|
|
<Button variant="secondary" size="sm" onClick={closeEditModal}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="primary" size="sm" onClick={handleEditSave} disabled={
|
|
!AuthService.canEditCalendarTab(currentTab) ||
|
|
(currentTab === 'activitiesCalendar' && (!editEventTitle || !editActivityCategory)) ||
|
|
(currentTab === 'incidentsCalendar' && (!editAttendanceCustomer || !editEventStartDateTime)) ||
|
|
(currentTab === 'mealPlanCalendar' && (!editEventTitle || !editMealType)) ||
|
|
(currentTab === 'reminderDatesCalendar' && (!editReminderTitleCategory || !editReminderAssociatedEntity))
|
|
}>
|
|
Save
|
|
</Button>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
|
|
{/* Delete Confirmation Modal */}
|
|
<Modal show={showDeleteConfirm} onHide={() => { setShowDeleteConfirm(false); setDeleteTargetId(null); }} size="sm" centered>
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>Confirm Delete</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
Are you sure you want to delete this event?
|
|
</Modal.Body>
|
|
<Modal.Footer>
|
|
<Button variant="secondary" size="sm" onClick={() => { setShowDeleteConfirm(false); setDeleteTargetId(null); }}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="danger" size="sm" onClick={() => { disableEvent(deleteTargetId); setShowDeleteConfirm(false); setDeleteTargetId(null); }}>
|
|
Delete
|
|
</Button>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
};
|
|
|
|
export default EventsCalendar; |