This commit is contained in:
Yang Li 2026-01-29 21:02:14 -05:00
parent c1991211aa
commit 36a16fa658
22 changed files with 3824 additions and 0 deletions

View File

@ -0,0 +1,119 @@
const { splitSite } = require("../middlewares");
const db = require("../models");
const DailyRoutesTemplate = db.dailyRoutesTemplate;
// Create and Save a new Daily Routes Template
exports.createDailyRoutesTemplate = (req, res) => {
// Validate request
if (!req.body.name) {
res.status(400).send({ message: "Content can not be empty!" });
return;
}
const site = splitSite.findSiteNumber(req);
// Create a Daily Routes Template
const dailyRoutesTemplate = new DailyRoutesTemplate({
name: req.body.name,
template_date: req.body.template_date,
routes: req.body.routes || [],
site,
create_by: req.body.create_by || '',
create_date: new Date()
});
// Save Daily Routes Template in the database
dailyRoutesTemplate
.save(dailyRoutesTemplate)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Daily Routes Template."
});
});
};
// Retrieve all Daily Routes Templates from the database
exports.getAllDailyRoutesTemplates = (req, res) => {
var params = req.query;
var condition = {};
condition = splitSite.splitSiteGet(req, condition);
if (params.template_date) {
condition.template_date = params.template_date;
}
DailyRoutesTemplate.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Daily Routes Templates."
});
});
};
// Get One Daily Routes Template by Id
exports.getDailyRoutesTemplate = (req, res) => {
const id = req.params.id;
DailyRoutesTemplate.findById(id)
.then(data => {
if (!data)
res.status(404).send({ message: "Not found Daily Routes Template with id " + id });
else res.send(data);
})
.catch(err => {
res
.status(500)
.send({ message: "Error retrieving Daily Routes Template with id=" + id });
});
};
// Update a Daily Routes Template by the id in the request
exports.updateDailyRoutesTemplate = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const id = req.params.id;
DailyRoutesTemplate.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update Daily Routes Template with id=${id}. Maybe Daily Routes Template was not found!`
});
} else res.send({ success: true, message: "Daily Routes Template was updated successfully." });
})
.catch(err => {
res.status(500).send({
success: false,
message: "Error updating Daily Routes Template with id=" + id
});
});
};
// Delete a Daily Routes Template by id
exports.deleteDailyRoutesTemplate = (req, res) => {
const id = req.params.id;
DailyRoutesTemplate.findByIdAndRemove(id)
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete Daily Routes Template with id=${id}. Maybe Daily Routes Template was not found!`
});
} else {
res.send({
message: "Daily Routes Template was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Could not delete Daily Routes Template with id=" + id
});
});
};

View File

@ -0,0 +1,198 @@
const db = require("../models");
const FingerprintAttendance = db.fingerprint_attendance;
const { splitSite } = require("../middlewares");
// Create and Save a new Fingerprint Attendance record
exports.createFingerprintAttendance = (req, res) => {
// Validate request
if (!req.body.employee) {
res.status(400).send({ message: "Employee is required!" });
return;
}
const site = splitSite.findSiteNumber(req);
// Create a Fingerprint Attendance record
const fingerprintAttendance = new FingerprintAttendance({
employee: req.body.employee,
finger_print: req.body.finger_print || '',
finger_print_time: req.body.finger_print_time || new Date(),
create_date: req.body.create_date || new Date(),
create_by: req.body.create_by || '',
update_date: req.body.update_date || new Date(),
update_by: req.body.update_by || '',
note: req.body.note || '',
site: req.body.site
});
// Save Fingerprint Attendance in the database
fingerprintAttendance
.save(fingerprintAttendance)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Fingerprint Attendance record."
});
});
};
// Retrieve all Fingerprint Attendance records from the database
exports.getAllFingerprintAttendances = (req, res) => {
var params = req.query;
var condition = {};
condition = splitSite.splitSiteGet(req, condition);
// Add filters based on query parameters
if (params.employee) {
condition.employee = params.employee;
}
if (params.finger_print) {
condition.finger_print = params.finger_print;
}
if (params.create_by) {
condition.create_by = params.create_by;
}
if (params.site) {
condition.site = params.site;
}
FingerprintAttendance.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving fingerprint attendance records."
});
});
};
// Get One Fingerprint Attendance record by Id
exports.getFingerprintAttendance = (req, res) => {
const id = req.params.id;
FingerprintAttendance.findById(id)
.then(data => {
if (!data)
res.status(404).send({ message: "Not found Fingerprint Attendance record with id " + id });
else res.send(data);
})
.catch(err => {
res
.status(500)
.send({ message: "Error retrieving Fingerprint Attendance record with id=" + id });
});
};
// Update a Fingerprint Attendance record by the id in the request
exports.updateFingerprintAttendance = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const id = req.params.id;
// Add update_date and update_by to the request body
req.body.update_date = new Date();
FingerprintAttendance.findByIdAndUpdate(id, req.body, { useFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update fingerprint attendance record with id=${id}. Maybe Fingerprint Attendance record was not found!`
});
} else res.send({ success: true, message: "Fingerprint Attendance record was updated successfully." });
})
.catch(err => {
res.status(500).send({
success: false,
message: "Error updating Fingerprint Attendance record with id=" + id
});
});
};
// Delete a Fingerprint Attendance record by id
exports.deleteFingerprintAttendance = (req, res) => {
const id = req.params.id;
FingerprintAttendance.findByIdAndRemove(id)
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot delete fingerprint attendance record with id=${id}. Maybe Fingerprint Attendance record was not found!`
});
} else {
res.send({
message: "Fingerprint Attendance record was deleted successfully!"
});
}
})
.catch(err => {
res.status(500).send({
message: "Could not delete Fingerprint Attendance record with id=" + id
});
});
};
// Get Fingerprint Attendance records by employee
exports.getFingerprintAttendancesByEmployee = (req, res) => {
const employee = req.params.employee;
var params = req.query;
var condition = { employee: employee };
condition = splitSite.splitSiteGet(req, condition);
// Add additional filters
if (params.start_date && params.end_date) {
condition.finger_print_time = { $gte: new Date(params.start_date), $lte: new Date(params.end_date) };
}
if (params.site) {
condition.site = params.site;
}
FingerprintAttendance.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving fingerprint attendance records for employee " + employee
});
});
};
// Get Fingerprint Attendance records by date range
exports.getFingerprintAttendancesByDateRange = (req, res) => {
var params = req.query;
if (!params.start_date || !params.end_date) {
res.status(400).send({ message: "Start date and end date are required!" });
return;
}
var condition = {
finger_print_time: { $gte: new Date(params.start_date), $lte: new Date(params.end_date) }
};
condition = splitSite.splitSiteGet(req, condition);
// Add additional filters
if (params.employee) {
condition.employee = params.employee;
}
if (params.site) {
condition.site = params.site;
}
FingerprintAttendance.find(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving fingerprint attendance records for date range."
});
});
};

View File

@ -0,0 +1,80 @@
const uniqueValidator = require('mongoose-unique-validator');
module.exports = mongoose => {
// Import the route customer list schema from route-path
var routeCustomerListSchema = mongoose.Schema({
customer_id: String,
customer_name: String,
customer_address: String,
customer_avatar: String,
customer_group: String,
customer_group_address: String,
customer_type: String,
customer_pickup_status: String,
customer_note: String,
customer_special_needs: String,
customer_phone: String,
customer_enter_center_time: Date,
customer_leave_center_time: Date,
customer_pickup_time: Date,
customer_dropoff_time: Date,
customer_route_status: String,
customer_pickup_order: Number,
customer_table_id: String,
customer_transfer_to_route: String,
customer_language: String,
customer_estimated_pickup_time: String,
customer_estimated_dropoff_time: String,
customer_address_override: String,
});
var checklistResultSchema = mongoose.Schema({
item: String,
result: Boolean
});
// Route path schema to store in the template
var routePathSchema = mongoose.Schema({
name: String,
schedule_date: String,
vehicle: String,
status: [{
type: String
}],
driver: String,
type: String,
start_mileage: Number,
end_mileage: Number,
start_time: Date,
end_time: Date,
estimated_start_time: Date,
route_customer_list: [{
type: routeCustomerListSchema
}],
checklist_result: [{
type: checklistResultSchema,
}],
});
var schema = mongoose.Schema(
{
name: String,
template_date: String,
routes: [{
type: routePathSchema
}],
site: Number,
create_by: String,
create_date: Date
},
{ collection: 'daily_routes_template', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
schema.plugin(uniqueValidator);
const DailyRoutesTemplate = mongoose.model("daily_routes_template", schema);
return DailyRoutesTemplate;
};

View File

@ -0,0 +1,23 @@
module.exports = (mongoose) => {
var schema = mongoose.Schema(
{
employee: String,
finger_print: String,
finger_print_time: Date,
create_date: Date,
create_by: String,
update_date: Date,
update_by: String,
note: String,
site: Number
},
{ collection: 'fingerprint_attendance', timestamps: true }
);
schema.method("toJSON", function() {
const { __v, _id, ...object } = this.toObject();
object.id = _id;
return object;
});
const FingerprintAttendance = mongoose.model("fingerprint_attendance", schema);
return FingerprintAttendance;
};

View File

@ -0,0 +1,24 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const dailyRoutesTemplates = require("../controllers/daily-routes-template.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Retrieve all daily routes templates
router.get("/", [authJwt.verifyToken], dailyRoutesTemplates.getAllDailyRoutesTemplates);
// Get one daily routes template by id
router.get("/:id", [authJwt.verifyToken], dailyRoutesTemplates.getDailyRoutesTemplate);
// Create a new daily routes template
router.post("/", [authJwt.verifyToken], dailyRoutesTemplates.createDailyRoutesTemplate);
// Update a daily routes template with id
router.put("/:id", [authJwt.verifyToken], dailyRoutesTemplates.updateDailyRoutesTemplate);
// Delete a daily routes template with id
router.delete("/:id", [authJwt.verifyToken], dailyRoutesTemplates.deleteDailyRoutesTemplate);
app.use('/api/daily-routes-templates', router);
};

View File

@ -0,0 +1,35 @@
const {authJwt} = require("../middlewares");
module.exports = app => {
const fingerprintAttendance = require("../controllers/fingerprint-attendance.controller.js");
app.use((req, res, next) => {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
var router = require("express").Router();
// Create a new Fingerprint Attendance record
router.post("/", fingerprintAttendance.createFingerprintAttendance);
// Retrieve all Fingerprint Attendance records
router.get("/", [authJwt.verifyToken], fingerprintAttendance.getAllFingerprintAttendances);
// Get Fingerprint Attendance records by date range (MUST come before /:id route)
router.get("/date-range", [authJwt.verifyToken], fingerprintAttendance.getFingerprintAttendancesByDateRange);
// Get Fingerprint Attendance records by employee (MUST come before /:id route)
router.get("/employee/:employee", [authJwt.verifyToken], fingerprintAttendance.getFingerprintAttendancesByEmployee);
// Retrieve a single Fingerprint Attendance record with id
router.get("/:id", [authJwt.verifyToken], fingerprintAttendance.getFingerprintAttendance);
// Update a Fingerprint Attendance record with id
router.put("/:id", [authJwt.verifyToken], fingerprintAttendance.updateFingerprintAttendance);
// Delete a Fingerprint Attendance record with id
router.delete("/:id", [authJwt.verifyToken], fingerprintAttendance.deleteFingerprintAttendance);
app.use('/api/fingerprint-attendance', router);
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,140 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* @kurkle/color v0.3.4
* https://github.com/kurkle/color#readme
* (c) 2024 Jukka Kurkela
* Released under the MIT License
*/
/*!
* Chart.js v4.5.0
* https://www.chartjs.org
* (c) 2025 Chart.js Contributors
* Released under the MIT License
*/
/*!
* Signature Pad v2.3.2
* https://github.com/szimek/signature_pad
*
* Copyright 2017 Szymon Nowak
* Released under the MIT license
*
* The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
* http://corner.squareup.com/2012/07/smoother-signatures.html
*
* Implementation of interpolation using cubic Bézier curves is taken from:
* http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
*
* Algorithm for approximated length of a Bézier curve is taken from:
* http://www.lemoda.net/maths/bezier-length/index.html
*
*/
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* use-sync-external-store-shim.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* use-sync-external-store-shim/with-selector.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* React Router v6.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
//! moment.js

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,751 @@
import React, {useState, useEffect} 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 } 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 } from "react-bootstrap-icons";
import DatePicker from "react-datepicker";
import { vehicleSlice } from "../../store";
// import { Scheduler } from "@aldabil/react-scheduler";
const EventsCalendar = () => {
const navigate = useNavigate();
const [events, setEvents] = useState([]);
const [allEvents, setAllEvents] = useState([]);
const [targetedEventType, setTargetedEventType] = useState('medical');
const [currentTab, setCurrentTab] = useState('medicalCalendar');
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 [timeData, setTimeData] = useState([]);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const eventsServicePlugin = createEventsServicePlugin();
const eventModalService = createEventModalPlugin();
const eventRecurrence = createEventRecurrencePlugin();
const [groupedEvents, setGroupedEvents] = useState(new Map());
const [currentView, setCurrentView] = useState('month');
const [currentViewDate, setCurrentViewDate] = useState(new Date());
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([]);
// 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
const formatEventTitle = (customerName, startTime) => {
const fullName = formatFullName(customerName);
const timeStr = startTime ? moment(new Date(startTime)).format('hh:mm A') : '';
// Try: "Time: Full Name"
const titleWithTime = timeStr ? `${timeStr}: ${fullName}` : fullName;
if (titleWithTime.length <= 35) {
return fullName;
}
// Try: "Full Name"
if (fullName.length <= 35) {
return fullName;
}
// Use: "FirstName L"
return getShortenedName(customerName);
};
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: {
onSelectedDateUpdate(date) {
setCurrentViewDate(new Date(date));
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
},
onViewChange(view) {
setCurrentView(view);
},
onClickDate(date) {
setNewEventStartDateTime(new Date(date))
setNewEventEndDateTime(new Date(date));
setShowCreationModal(true);
},
onClickDateTime(dateTime) {
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
setShowCreationModal(true);
}
}
});
// Filter events based on current view
const getFilteredEvents = () => {
const viewDate = moment(currentViewDate);
// Check for day view
if (currentView && currentView.toLowerCase().includes('day')) {
// Show only events for the selected day
return events.filter(event => {
const eventDate = moment(event.start_time);
return eventDate.isSame(viewDate, 'day');
});
}
// Check for week view
else if (currentView && currentView.toLowerCase().includes('week')) {
// Show events for the week containing the selected date
const startOfWeek = viewDate.clone().startOf('week');
const endOfWeek = viewDate.clone().endOf('week');
return events.filter(event => {
const eventDate = moment(event.start_time);
return eventDate.isBetween(startOfWeek, endOfWeek, 'day', '[]');
});
}
// Month view (including month-grid and month-agenda)
else {
return events.filter(event => {
const eventDate = moment(event.start_time);
return eventDate.isSame(viewDate, 'month');
});
}
};
const getGroupedEvents = () => {
const eventsDateMap = new Map();
const filteredEvents = getFilteredEvents();
for (const eventItem of filteredEvents) {
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);
}
}
return eventsDateMap;
};
useEffect(() => {
if (!AuthService.canAccessLegacySystem()) {
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`);
}
VehicleService.getAllActiveVehicles().then((data) => {
setVehicles(data.data)
});
EmployeeService.getAllEmployees().then((data) => {
setEmployees(data.data);
});
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data);
});
ResourceService.getAll().then((data) => {
setResources(data.data);
});
EventsService.getTimeData().then(data => {
setTimeData(data.data);
});
}, []);
useEffect(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
}, [fromDate, toDate]);
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.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));
}
} else {
const originalEvents = [...allEvents];
setEvents(originalEvents?.filter(item => item.type === eventTypeMap[currentTab])?.map(item => ({
...item,
title: item?.title,
start: item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`,
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')}`),
_options: { additionalClasses: [`event-${item?.color || 'primary'}`]}
}))?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
}
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
useEffect(() => {
if (events && calendar) {
calendar?.eventsService?.set(events);
setGroupedEvents(getGroupedEvents());
}
}, [events, currentView, currentViewDate]);
const redirectToAdmin = () => {
navigate(`/medical`)
}
const goToEdit = (id) => {
navigate(`/medical/events/edit/${id}?from=calendar`)
}
const goToCreateNew = () => {
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) => {
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'));
// }
})
});
}
const FilterAndClose = () => {
setShowFilterDropdown(false);
}
const cleanFilterAndClose = () => {
setShowFilterDropdown(false);
setShowDeletedItems(false);
}
const goToTab = (value) => {
setTargetedEventType(eventTypeMap[value]);
setCurrentTab(value);
}
const customComponents = {
eventModal: ({calendarEvent}) => {
return <>
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</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">
{currentTab === 'medicalCalendar' && <PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>}
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </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 className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Show Deleted Events</div>
<input type="checkbox" value={showDeletedItems} checked={showDeletedItems === true} onClick={() => setShowDeletedItems(!showDeletedItems)} />
</div>
</div>
<div className="list row">
<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>
);
},
);
const calendarView = <div className="multi-columns-container">
<div className="column-container" style={{'minWidth': '1000px'}}>
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
</div>
<div className="column-container">
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden' }}>
<h6 className="text-primary me-4">List</h6>
{
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-4 event-list-item-container`}
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default' }}
>
<div className="event-item-flex">
<div className="sx__month-agenda-event__title">
{currentTab === 'medicalCalendar'
? `${moment(eventItem?.start_time).format('hh:mm A')}: ${formatFullName(eventItem.customer)}`
: `${moment(eventItem?.start_time).format('hh:mm A')}: ${eventItem.title}`
}
</div>
</div>
<div className="sx__event-modal__time with-padding">{ currentTab === 'medicalCalendar' ? `provider: ${eventItem?.doctor}` : eventItem?.description}</div>
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Vehicle: ${eventItem?.target_name}`}</div>}
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Deadline: ${moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}`}</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);
}
const handleSave = () => {
const data = {
title: newEventTitle,
description: newEventDescription,
type: newEventType,
department: newEventDepartment,
start_time: newEventStartDateTime,
stop_time: newEventStartDateTime,
color: newEventColor,
source_type: newEventSourceType,
source_uuid: newEventSource?.value,
source_name: newEventSource?.label,
target_type: newEventTargetType,
target_uuid: newEventTarget?.value,
target_name: newEventTarget?.label,
event_location: newEventLocation,
event_prediction_date: newEventFutureDate && moment(newEventFutureDate).format('MM/DD/YYYY'),
event_reminder_type: newEventReminderType,
rrule: newEventRecurring,
status: 'active',
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
edit_date: new Date(),
create_date: new Date(),
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
}
EventsService.createNewEvent(data).then(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
setAllEvents(data.data);
setShowCreationModal(false);
setNewEventDescription('');
setNewEventTitle('');
setNewEventLocation('');
setNewEventSource(undefined);
setNewEventTarget(undefined);
setNewEventStartDateTime(undefined);
setNewEventType('');
})
});
}
return (
<>
<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 defaultActiveKey="medicalCalendar" id="medical-calendar-tab" onSelect={(k) => goToTab(k)}>
<Tab eventKey="medicalCalendar" title="Medical Appointments">
</Tab>
<Tab eventKey="activitiesCalendar" title="Activities">
</Tab>
<Tab eventKey="incidentsCalendar" title="Important Notes And Incidents">
</Tab>
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
</Tab>
<Tab eventKey="reminderDatesCalendar" title="Important Dates">
</Tab>
</Tabs>
{ calendarView}
<div className="list-func-panel">
<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 && currentTab !== 'medicalCalendar'} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Create New Calendar Item</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Title
<span className="required">*</span>
</div>
<input type="text" placeholder="Briefly Describe activity, incident, menu or reminder" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Description
<span className="required">*</span>
</div>
<textarea type="text" placeholder="Details for activity, incident, menu item and ingredients or reminder," value={newEventDescription || ''} onChange={e => setNewEventDescription(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Type
<span className="required">*</span>
</div>
<div className="field-value">{newEventType}</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Want event to be recurring?
<select value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
<option value=""></option>
<option value="FREQ=YEARLY">Yearly</option>
<option value="FREQ=MONTHLY">Monthly</option>
<option value="FREQ=WEEKLY">Weekly</option>
<option value="FREQ=DAILY">Daily</option>
</select>
</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Start Time
<span className="required">*</span>
</div>
<DatePicker
selected={newEventStartDateTime}
onChange={setNewEventStartDateTime}
showTimeInput
timeInputLabel="Time:"
dateFormat="MM/dd/yyyy, HH:mm"
></DatePicker>
</div>
<div className="me-4">
<div className="field-label">End Time
<span className="required">*</span>
<span className="field-blurb float-right">Make sure end time is later than start time </span>
</div>
<DatePicker
selected={newEventEndDateTime}
onChange={setNewEventEndDateTime}
showTimeInput
timeInputLabel="Time:"
dateFormat="MM/dd/yyyy, HH:mm"
></DatePicker>
</div>
</div>
{ currentTab !== 'mealPlanCalendar' && <> <div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Who will we take care of in this event:</div>
<select value={newEventTargetType} onChange={(e) => setNewEventTargetType(e.target.value)}>
<option value=""></option>
<option value="vehicle">Vehicle</option>
<option value="customer">Customer</option>
<option value="employee">Employee</option>
<option value="notApplicable">Not Applicable</option>
</select>
</div>
{newEventTargetType && newEventTargetType !== '' && newEventTargetType !== 'notApplicable' && <div className="me-4">
<div className="field-label">Please select a candidate</div>
<select value={newEventTarget?.value || ''} onChange={(e) => {
const selectedOption = e.target.options[e.target.selectedIndex];
setNewEventTarget({
value: selectedOption.value,
label: selectedOption.text
})
}}>
<option value=""></option>
{
newEventTargetType === 'vehicle' && vehicles.map((item) => <option value={item?.id}>{item?.vehicle_number}</option>)
}
{
newEventTargetType === 'customer' && customers.map((item) => <option value={item?.id}>{item?.name}</option>)
}
{
newEventTargetType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
}
</select>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Who will be responsible for this event</div>
<select value={newEventSourceType} onChange={(e) => setNewEventSourceType(e.target.value)}>
<option value=""></option>
<option value="employee">Employee</option>
<option value="resource">Resource</option>
<option value="notApplicable">Not Applicable</option>
</select>
</div>
{newEventSourceType && newEventSourceType !== '' && newEventSourceType !== 'notApplicable' && <div className="me-4">
<div className="field-label">Please select a member</div>
<select value={newEventSource?.value || ''} onChange={(e) => {
const selectedOption = e.target.options[e.target.selectedIndex];
setNewEventSource({
value: selectedOption.value,
label: selectedOption.text
})
}}>
<option value=""></option>
{
newEventSourceType === 'resource' && resources.map((item) => <option value={item?.id}>{item?.name}</option>)
}
{
newEventSourceType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
}
</select>
</div>}
</div> </>}
<div className="app-main-content-fields-section">
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
<div className="field-label">Reminder Type
</div>
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
<option value=""></option>
<option value="birthday">Birthday</option>
<option value="membership">Membership Renew</option>
<option value="payment">Customer Payment Due Date</option>
<option value="insurance_renew">Customer Insurance Renew</option>
<option value="insurance_expire">Vehicle Insurance Renew</option>
<option value="title_expire">Vehicle Title Registration</option>
<option value="emission_test">Vehicle Emission Test</option>
<option value="oil_change">Vehicle Oil Change</option>
</select>
</div>}
{currentTab === 'activitiesCalendar' && <div className="me-4">
<div className="field-label">Event Location
</div>
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
</div>}
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
</div>
<DatePicker selected={newEventFutureDate}
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Color
</div>
<select value={newEventColor} onChange={e => setNewEventColor(e.target.value)}>
<option value=""></option>
{
EventsService.colorOptions?.map((item) => <option value={item?.value}>{item?.label}</option>)
}
</select>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleSave}>
Save Calendar Item
</Button>
</Modal.Footer>
</Modal>
</div>
</div>
</>
)
};
export default EventsCalendar;

View File

@ -0,0 +1,751 @@
import React, {useState, useEffect} 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 } 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 } from "react-bootstrap-icons";
import DatePicker from "react-datepicker";
import { vehicleSlice } from "../../store";
// import { Scheduler } from "@aldabil/react-scheduler";
const EventsCalendar = () => {
const navigate = useNavigate();
const [events, setEvents] = useState([]);
const [allEvents, setAllEvents] = useState([]);
const [targetedEventType, setTargetedEventType] = useState('medical');
const [currentTab, setCurrentTab] = useState('medicalCalendar');
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 [timeData, setTimeData] = useState([]);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const eventsServicePlugin = createEventsServicePlugin();
const eventModalService = createEventModalPlugin();
const eventRecurrence = createEventRecurrencePlugin();
const [groupedEvents, setGroupedEvents] = useState(new Map());
const [currentView, setCurrentView] = useState('month');
const [currentViewDate, setCurrentViewDate] = useState(new Date());
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([]);
// 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
const formatEventTitle = (customerName, startTime) => {
const fullName = formatFullName(customerName);
const timeStr = startTime ? moment(new Date(startTime)).format('hh:mm A') : '';
// Try: "Time: Full Name"
const titleWithTime = timeStr ? `${timeStr}: ${fullName}` : fullName;
if (titleWithTime.length <= 35) {
return fullName;
}
// Try: "Full Name"
if (fullName.length <= 35) {
return fullName;
}
// Use: "FirstName L"
return getShortenedName(customerName);
};
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: {
onSelectedDateUpdate(date) {
setCurrentViewDate(new Date(date));
setFromDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth(), 1));
setToDate(new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, 0));
},
onViewChange(view) {
setCurrentView(view);
},
onClickDate(date) {
setNewEventStartDateTime(new Date(date))
setNewEventEndDateTime(new Date(date));
setShowCreationModal(true);
},
onClickDateTime(dateTime) {
setNewEventStartDateTime(new Date(dateTime.replace(' ', 'T')));
setNewEventEndDateTime(new Date(dateTime.replace(' ', 'T')));
setShowCreationModal(true);
}
}
});
// Filter events based on current view
const getFilteredEvents = () => {
const viewDate = moment(currentViewDate);
// Check for day view
if (currentView && currentView.toLowerCase().includes('day')) {
// Show only events for the selected day
return events.filter(event => {
const eventDate = moment(event.start_time);
return eventDate.isSame(viewDate, 'day');
});
}
// Check for week view
else if (currentView && currentView.toLowerCase().includes('week')) {
// Show events for the week containing the selected date
const startOfWeek = viewDate.clone().startOf('week');
const endOfWeek = viewDate.clone().endOf('week');
return events.filter(event => {
const eventDate = moment(event.start_time);
return eventDate.isBetween(startOfWeek, endOfWeek, 'day', '[]');
});
}
// Month view (including month-grid and month-agenda)
else {
return events.filter(event => {
const eventDate = moment(event.start_time);
return eventDate.isSame(viewDate, 'month');
});
}
};
const getGroupedEvents = () => {
const eventsDateMap = new Map();
const filteredEvents = getFilteredEvents();
for (const eventItem of filteredEvents) {
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);
}
}
return eventsDateMap;
};
useEffect(() => {
if (!AuthService.canAccessLegacySystem()) {
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`);
}
VehicleService.getAllActiveVehicles().then((data) => {
setVehicles(data.data)
});
EmployeeService.getAllEmployees().then((data) => {
setEmployees(data.data);
});
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data);
});
ResourceService.getAll().then((data) => {
setResources(data.data);
});
EventsService.getTimeData().then(data => {
setTimeData(data.data);
});
}, []);
useEffect(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
}, [fromDate, toDate]);
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.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));
}
} else {
const originalEvents = [...allEvents];
setEvents(originalEvents?.filter(item => item.type === eventTypeMap[currentTab])?.map(item => ({
...item,
title: item?.title,
start: item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`,
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')}`),
_options: { additionalClasses: [`event-${item?.color || 'primary'}`]}
}))?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems));
}
}, [customers, resources, timeData, currentTab, allEvents, showDeletedItems])
useEffect(() => {
if (events && calendar) {
calendar?.eventsService?.set(events);
setGroupedEvents(getGroupedEvents());
}
}, [events, currentView, currentViewDate]);
const redirectToAdmin = () => {
navigate(`/medical`)
}
const goToEdit = (id) => {
navigate(`/medical/events/edit/${id}?from=calendar`)
}
const goToCreateNew = () => {
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) => {
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'));
// }
})
});
}
const FilterAndClose = () => {
setShowFilterDropdown(false);
}
const cleanFilterAndClose = () => {
setShowFilterDropdown(false);
setShowDeletedItems(false);
}
const goToTab = (value) => {
setTargetedEventType(eventTypeMap[value]);
setCurrentTab(value);
}
const customComponents = {
eventModal: ({calendarEvent}) => {
return <>
<div className="sx__event-modal__title">{currentTab === 'medicalCalendar' ? calendarEvent?.customer : calendarEvent?.title}</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">
{currentTab === 'medicalCalendar' && <PencilSquare size={16} onClick={() => goToEdit(calendarEvent?.id)} className="me-4"></PencilSquare>}
<Archive size={16} onClick={() =>{disableEvent(calendarEvent?.id)}}></Archive> </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 className="app-main-content-fields-section margin-sm dropdown-container">
<div className="me-4">
<div className="field-label">Show Deleted Events</div>
<input type="checkbox" value={showDeletedItems} checked={showDeletedItems === true} onClick={() => setShowDeletedItems(!showDeletedItems)} />
</div>
</div>
<div className="list row">
<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>
);
},
);
const calendarView = <div className="multi-columns-container">
<div className="column-container" style={{'minWidth': '1000px'}}>
{calendar && <ScheduleXCalendar customComponents={customComponents} calendarApp={calendar} />}
</div>
<div className="column-container">
<div className="column-card" style={{ maxHeight: '800px', overflowY: 'auto', overflowX: 'hidden' }}>
<h6 className="text-primary me-4">List</h6>
{
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-4 event-list-item-container`}
onClick={() => currentTab === 'medicalCalendar' && goToView(eventItem.id)}
style={{ cursor: currentTab === 'medicalCalendar' ? 'pointer' : 'default' }}
>
<div className="event-item-flex">
<div className="sx__month-agenda-event__title">
{currentTab === 'medicalCalendar'
? `${moment(eventItem?.start_time).format('hh:mm A')}: ${formatFullName(eventItem.customer)}`
: `${moment(eventItem?.start_time).format('hh:mm A')}: ${eventItem.title}`
}
</div>
</div>
<div className="sx__event-modal__time with-padding">{ currentTab === 'medicalCalendar' ? `provider: ${eventItem?.doctor}` : eventItem?.description}</div>
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Vehicle: ${eventItem?.target_name}`}</div>}
{eventItem?.event_prediction_date && <div className="sx__event-modal__time with-padding">{ `Deadline: ${moment(eventItem?.event_prediction_date).format('MM/DD/YYYY')}`}</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);
}
const handleSave = () => {
const data = {
title: newEventTitle,
description: newEventDescription,
type: newEventType,
department: newEventDepartment,
start_time: newEventStartDateTime,
stop_time: newEventStartDateTime,
color: newEventColor,
source_type: newEventSourceType,
source_uuid: newEventSource?.value,
source_name: newEventSource?.label,
target_type: newEventTargetType,
target_uuid: newEventTarget?.value,
target_name: newEventTarget?.label,
event_location: newEventLocation,
event_prediction_date: newEventFutureDate && moment(newEventFutureDate).format('MM/DD/YYYY'),
event_reminder_type: newEventReminderType,
rrule: newEventRecurring,
status: 'active',
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
edit_date: new Date(),
create_date: new Date(),
edit_history: [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }]
}
EventsService.createNewEvent(data).then(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => {
setAllEvents(data.data);
setShowCreationModal(false);
setNewEventDescription('');
setNewEventTitle('');
setNewEventLocation('');
setNewEventSource(undefined);
setNewEventTarget(undefined);
setNewEventStartDateTime(undefined);
setNewEventType('');
})
});
}
return (
<>
<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 defaultActiveKey="medicalCalendar" id="medical-calendar-tab" onSelect={(k) => goToTab(k)}>
<Tab eventKey="medicalCalendar" title="Medical Appointments">
</Tab>
<Tab eventKey="activitiesCalendar" title="Activities">
</Tab>
<Tab eventKey="incidentsCalendar" title="Important Notes And Incidents">
</Tab>
<Tab eventKey="mealPlanCalendar" title="Meal Plan">
</Tab>
<Tab eventKey="reminderDatesCalendar" title="Important Dates">
</Tab>
</Tabs>
{ calendarView}
<div className="list-func-panel">
<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 && currentTab !== 'medicalCalendar'} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Create New Calendar Item</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Title
<span className="required">*</span>
</div>
<input type="text" placeholder="Briefly Describe activity, incident, menu or reminder" value={newEventTitle || ''} onChange={e => setNewEventTitle(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Description
<span className="required">*</span>
</div>
<textarea type="text" placeholder="Details for activity, incident, menu item and ingredients or reminder," value={newEventDescription || ''} onChange={e => setNewEventDescription(e.target.value)}/>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Type
<span className="required">*</span>
</div>
<div className="field-value">{newEventType}</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Want event to be recurring?
<select value={newEventRecurring} onChange={(e) => setNewEventRecurring(e.target.value)}>
<option value=""></option>
<option value="FREQ=YEARLY">Yearly</option>
<option value="FREQ=MONTHLY">Monthly</option>
<option value="FREQ=WEEKLY">Weekly</option>
<option value="FREQ=DAILY">Daily</option>
</select>
</div>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Start Time
<span className="required">*</span>
</div>
<DatePicker
selected={newEventStartDateTime}
onChange={setNewEventStartDateTime}
showTimeInput
timeInputLabel="Time:"
dateFormat="MM/dd/yyyy, HH:mm"
></DatePicker>
</div>
<div className="me-4">
<div className="field-label">End Time
<span className="required">*</span>
<span className="field-blurb float-right">Make sure end time is later than start time </span>
</div>
<DatePicker
selected={newEventEndDateTime}
onChange={setNewEventEndDateTime}
showTimeInput
timeInputLabel="Time:"
dateFormat="MM/dd/yyyy, HH:mm"
></DatePicker>
</div>
</div>
{ currentTab !== 'mealPlanCalendar' && <> <div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Who will we take care of in this event:</div>
<select value={newEventTargetType} onChange={(e) => setNewEventTargetType(e.target.value)}>
<option value=""></option>
<option value="vehicle">Vehicle</option>
<option value="customer">Customer</option>
<option value="employee">Employee</option>
<option value="notApplicable">Not Applicable</option>
</select>
</div>
{newEventTargetType && newEventTargetType !== '' && newEventTargetType !== 'notApplicable' && <div className="me-4">
<div className="field-label">Please select a candidate</div>
<select value={newEventTarget?.value || ''} onChange={(e) => {
const selectedOption = e.target.options[e.target.selectedIndex];
setNewEventTarget({
value: selectedOption.value,
label: selectedOption.text
})
}}>
<option value=""></option>
{
newEventTargetType === 'vehicle' && vehicles.map((item) => <option value={item?.id}>{item?.vehicle_number}</option>)
}
{
newEventTargetType === 'customer' && customers.map((item) => <option value={item?.id}>{item?.name}</option>)
}
{
newEventTargetType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
}
</select>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Who will be responsible for this event</div>
<select value={newEventSourceType} onChange={(e) => setNewEventSourceType(e.target.value)}>
<option value=""></option>
<option value="employee">Employee</option>
<option value="resource">Resource</option>
<option value="notApplicable">Not Applicable</option>
</select>
</div>
{newEventSourceType && newEventSourceType !== '' && newEventSourceType !== 'notApplicable' && <div className="me-4">
<div className="field-label">Please select a member</div>
<select value={newEventSource?.value || ''} onChange={(e) => {
const selectedOption = e.target.options[e.target.selectedIndex];
setNewEventSource({
value: selectedOption.value,
label: selectedOption.text
})
}}>
<option value=""></option>
{
newEventSourceType === 'resource' && resources.map((item) => <option value={item?.id}>{item?.name}</option>)
}
{
newEventSourceType === 'employee' && employees.map((item) => <option value={item?.id}>{item?.name}</option>)
}
</select>
</div>}
</div> </>}
<div className="app-main-content-fields-section">
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
<div className="field-label">Reminder Type
</div>
<select value={newEventReminderType} onChange={(e) => setNewEventReminderType(e.target.value)}>
<option value=""></option>
<option value="birthday">Birthday</option>
<option value="membership">Membership Renew</option>
<option value="payment">Customer Payment Due Date</option>
<option value="insurance_renew">Customer Insurance Renew</option>
<option value="insurance_expire">Vehicle Insurance Renew</option>
<option value="title_expire">Vehicle Title Registration</option>
<option value="emission_test">Vehicle Emission Test</option>
<option value="oil_change">Vehicle Oil Change</option>
</select>
</div>}
{currentTab === 'activitiesCalendar' && <div className="me-4">
<div className="field-label">Event Location
</div>
<input type="text" placeholder="Type in the location this event gonna happen if applicable" value={newEventLocation || ''} onChange={e => setNewEventLocation(e.target.value)}/>
</div>}
{currentTab === 'reminderDatesCalendar' && <div className="me-4">
<div className="field-label">If this is reminder which will happen later, please select the accurate Date it gonna happen:
</div>
<DatePicker selected={newEventFutureDate}
onChange={setNewEventFutureDate} dateFormat="MM/dd/yyyy"/>
</div>}
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Color
</div>
<select value={newEventColor} onChange={e => setNewEventColor(e.target.value)}>
<option value=""></option>
{
EventsService.colorOptions?.map((item) => <option value={item?.value}>{item?.label}</option>)
}
</select>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleSave}>
Save Calendar Item
</Button>
</Modal.Footer>
</Modal>
</div>
</div>
</>
)
};
export default EventsCalendar;

View File

@ -0,0 +1,311 @@
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
import { Modal, Button, Breadcrumb, Tabs, Tab } from "react-bootstrap";
import RouteCustomerEditor from "./RouteCustomerEditor";
import { AuthService, DailyRoutesTemplateService } from "../../services";
const CreateTemplateRoute = () => {
const params = useParams();
const templateId = params.id;
const routeType = new URLSearchParams(window.location.search).get('type');
const drivers = useSelector(selectAllActiveDrivers);
const vehicles = useSelector(selectAllActiveVehicles);
const navigate = useNavigate();
const [routeName, setRouteName] = useState('');
const [newDriver, setNewDriver] = useState('');
const [newVehicle, setNewVehicle] = useState('');
const [newRouteType, setNewRouteType] = useState(routeType || 'inbound');
const [newCustomerList, setNewCustomerList] = useState([]);
const [errorMessage, setErrorMessage] = useState(undefined);
const [disableSave, setDisableSave] = useState(false);
const [template, setTemplate] = useState(null);
const currentVehicle = vehicles.find((vehicle) => vehicle.id === newVehicle);
useEffect(() => {
if (!AuthService.canAddOrEditRoutes()) {
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`);
}
}, []);
useEffect(() => {
// Fetch the template to get its name
DailyRoutesTemplateService.getDailyRoutesTemplate(templateId).then(data => {
setTemplate(data.data);
});
}, [templateId]);
const redirectToTemplate = () => {
navigate(`/trans-routes/daily-templates/view/${templateId}`);
}
const validateRoute = () => {
const errors = [];
// Required fields validation
if (!routeName || routeName.trim() === '') {
errors.push('Route Name');
}
if (!newRouteType || newRouteType === '') {
errors.push('Route Type');
}
if (!newDriver || newDriver === '') {
errors.push('Driver');
}
if (!newVehicle || newVehicle === '') {
errors.push('Vehicle');
}
if (errors.length > 0) {
window.alert(`Please fill in the following required fields:\n${errors.join('\n')}`);
return false;
}
return true;
};
const saveRoute = () => {
if (!disableSave) {
if (!validateRoute()) {
return;
}
setDisableSave(true);
// Fetch the current template
DailyRoutesTemplateService.getDailyRoutesTemplate(templateId).then(response => {
const currentTemplate = response.data;
// Create the new route object
const newRoute = {
name: routeName,
route_customer_list: newCustomerList,
driver: newDriver,
vehicle: newVehicle,
type: newRouteType,
start_mileage: currentVehicle.mileage,
status: [],
start_time: null,
end_time: null,
estimated_start_time: null,
checklist_result: []
};
// Add the new route to the template's routes array
const updatedRoutes = [...(currentTemplate.routes || []), newRoute];
// Update the template
const updatedTemplate = {
...currentTemplate,
routes: updatedRoutes
};
DailyRoutesTemplateService.updateDailyRoutesTemplate(templateId, updatedTemplate)
.then(() => {
setTimeout(() => {
setDisableSave(false);
redirectToTemplate();
}, 1000);
})
.catch(err => {
setDisableSave(false);
setErrorMessage('Failed to save route to template');
});
}).catch(err => {
setDisableSave(false);
setErrorMessage('Failed to fetch template');
});
}
}
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
Daily Route Templates
</Breadcrumb.Item>
{template && (
<Breadcrumb.Item href={`/trans-routes/daily-templates/view/${templateId}`}>
{template.name}
</Breadcrumb.Item>
)}
<Breadcrumb.Item active>
Create New Route
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
Create New Route for Template <button className="btn btn-link btn-sm" onClick={() => {redirectToTemplate()}}>Back</button>
</h4>
</div>
</div>
<div className="app-main-content-list-container form-page">
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="routeOverview" id="route-view-tab">
<Tab eventKey="routeOverview" title="Route Information">
<div className="multi-columns-container">
<div className="column-container">
<div className="column-card">
<h6 className="text-primary">Route Details</h6>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Route Name
<span className="required">*</span>
</div>
<input type="text" value={routeName || ''} onChange={e => setRouteName(e.target.value)}/>
</div>
<div className="me-4">
<div className="field-label">Vechile
<span className="required">*</span>
</div>
<select value={newVehicle} onChange={e => setNewVehicle(e.target.value)}>
<option value=""></option>
{vehicles.map((vehicle) => (<option key={vehicle.id} value={vehicle.id}>{vehicle.vehicle_number}</option>))}
</select>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="me-4">
<div className="field-label">Driver
<span className="required">*</span>
</div>
<select value={newDriver} onChange={e => setNewDriver(e.target.value)}>
<option value=""></option>
{drivers.map((driver) => <option key={driver.id} value={driver.id}>{driver.name}</option>)}
</select>
</div>
<div className="me-4">
<div className="field-label">Route Type
<span className="required">*</span>
</div>
<select value={newRouteType} onChange={e => setNewRouteType(e.target.value)}>
<option value="inbound">Inbound</option>
<option value="outbound">Outbound</option>
</select>
</div>
</div>
</div>
<div className="column-card adjust">
<div className="col-md-12 mb-4">
<RouteCustomerEditor currentRoute={undefined} setNewCustomerList={setNewCustomerList}></RouteCustomerEditor>
</div>
</div>
<div className="list row mb-5">
<div className="col-md-12 col-sm-12 col-xs-12">
<button className="btn btn-default btn-sm float-right" onClick={() => redirectToTemplate()}> Cancel </button>
<button className="btn btn-primary btn-sm float-right" disabled={disableSave} onClick={() => saveRoute()}> Save </button>
</div>
{errorMessage && <div className="col-md-12 col-sm-12 col-xs-12 alert alert-danger mt-4">{errorMessage}</div>}
</div>
</div>
<div className="column-container">
{ newVehicle && newVehicle !== '' && <div className="column-card mb-4">
<h6 className="text-primary">Vehicle Information</h6>
<div className="app-main-content-fields-section short">
<div className="field-body">
<div className="field-label">Vehicle Number</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.vehicle_number}</div>
</div>
<div className="field-body">
<div className="field-label">Seating Capacity</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.capacity}</div>
</div>
<div className="field-body">
<div className="field-label">Mileage</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.mileage}</div>
</div>
<div className="field-body">
<div className="field-label">Make</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.make}</div>
</div>
<div className="field-body">
<div className="field-label">Model</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.model}</div>
</div>
</div>
<div className="app-main-content-fields-section short">
<div className="field-body">
<div className="field-label">License Plate</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.tag}</div>
</div>
<div className="field-body">
<div className="field-label">Year</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.year}</div>
</div>
<div className="field-body">
<div className="field-label">GPS ID</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.gps_tag}</div>
</div>
<div className="field-body">
<div className="field-label">EZPass</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.ezpass}</div>
</div>
<div className="field-body">
<div className="field-label">Vin</div>
<div className="field-value">{vehicles.find(item => item.id === newVehicle)?.vin || ''}</div>
</div>
</div>
</div>}
{
newDriver && newDriver !== '' && <div className="column-card">
<h6 className="text-primary">Driver Information</h6>
<div className="text-primary">Personal Details</div>
<div className="app-main-content-fields-section short">
<div className="field-body">
<div className="field-label">Driver Name</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.name}</div>
</div>
<div className="field-body">
<div className="field-label">Preferred Name</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.name_cn}</div>
</div>
<div className="field-body">
<div className="field-label">Job Title</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.title}</div>
</div>
<div className="field-body">
<div className="field-label">Job Status</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.employment_status}</div>
</div>
</div>
<div className="app-main-content-fields-section short">
<div className="field-body">
<div className="field-label">Driver Capacity</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.driver_capacity}</div>
</div>
<div className="field-body">
<div className="field-label">Phone Number</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.phone}</div>
</div>
<div className="field-body">
<div className="field-label">Email</div>
<div className="field-value">{drivers.find(item => item.id === newDriver)?.email}</div>
</div>
</div>
</div>
}
</div>
</div>
</Tab>
</Tabs>
</div>
</div>
</>
);
};
export default CreateTemplateRoute;

View File

@ -0,0 +1,287 @@
import React, {useState, useEffect} from "react";
import { useNavigate } from "react-router-dom";
import { AuthService, DailyRoutesTemplateService } from "../../services";
import { Breadcrumb, Modal, Button, Tabs, Tab } from "react-bootstrap";
import { PencilSquare, Eye } from "react-bootstrap-icons";
import ReactPaginate from 'react-paginate';
const DailyTemplatesList = () => {
const navigate = useNavigate();
const [templates, setTemplates] = useState([]);
const [keyword, setKeyword] = useState('');
const [currentItems, setCurrentItems] = useState(null);
const [sorting, setSorting] = useState({key: '', order: ''});
const [selectedItems, setSelectedItems] = useState([]);
const [itemOffset, setItemOffset] = useState(0);
const [pageCount, setPageCount] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [templateToDelete, setTemplateToDelete] = useState(null);
const columns = [
{
key: 'name',
label: 'Template Name',
show: true
},
{
key: 'template_date',
label: 'Template Date',
show: true
},
{
key: 'routes_count',
label: 'Routes Count',
show: true
},
{
key: 'create_by',
label: 'Created By',
show: true
},
{
key: 'create_date',
label: 'Created Date',
show: true
}
];
useEffect(() => {
if (!AuthService.canAccessLegacySystem()) {
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`);
}
fetchTemplates();
}, []);
const fetchTemplates = () => {
DailyRoutesTemplateService.getAll().then(data => {
setTemplates(data.data);
});
};
useEffect(() => {
const endOffset = itemOffset + parseInt(itemsPerPage);
const newTemplates = templates?.filter(template =>
template?.name?.toLowerCase().includes(keyword.toLowerCase()) ||
template?.template_date?.toLowerCase().includes(keyword.toLowerCase())
);
const sortedTemplates = sorting.key === '' ? newTemplates : newTemplates.sort((a, b) => {
if (sorting.key === 'routes_count') {
return (a.routes?.length || 0) - (b.routes?.length || 0);
}
return a[sorting.key]?.toString().localeCompare(b[sorting.key]?.toString());
});
const results = sorting.order === 'asc' ? sortedTemplates : sortedTemplates.reverse();
setCurrentItems(results.slice(itemOffset, endOffset)?.map(item => ({
...item,
routes_count: item.routes?.length || 0,
create_date: item.create_date ? new Date(item.create_date).toLocaleDateString() : ''
})));
setPageCount(Math.ceil(newTemplates.length / itemsPerPage));
}, [templates, itemOffset, keyword, itemsPerPage, sorting]);
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % templates?.filter(template =>
template?.name?.toLowerCase().includes(keyword.toLowerCase()) ||
template?.template_date?.toLowerCase().includes(keyword.toLowerCase())
).length;
setItemOffset(newOffset);
setCurrentPage(event.selected + 1);
};
const handlePageSelect = (pageNo) => {
const newOffset = ((pageNo-1) * itemsPerPage) % templates?.filter(template =>
template?.name?.toLowerCase().includes(keyword.toLowerCase()) ||
template?.template_date?.toLowerCase().includes(keyword.toLowerCase())
).length;
setItemOffset(newOffset);
};
const getSortingImg = (key) => {
return sorting.key === key ? (sorting.order === 'asc' ? 'up_arrow' : 'down_arrow') : 'default';
}
const sortTableWithField = (key) => {
let newSorting = {
key,
order: 'asc',
}
if (sorting.key === key && sorting.order === 'asc') {
newSorting = {...newSorting, order: 'desc'};
}
setSorting(newSorting);
}
const goToView = (id) => {
navigate(`/trans-routes/daily-templates/view/${id}`);
}
const confirmDelete = (template) => {
setTemplateToDelete(template);
setShowDeleteModal(true);
}
const deleteTemplate = () => {
if (templateToDelete) {
DailyRoutesTemplateService.deleteDailyRoutesTemplate(templateToDelete.id).then(() => {
fetchTemplates();
setShowDeleteModal(false);
setTemplateToDelete(null);
});
}
}
const toggleSelectedAllItems = () => {
if (selectedItems.length !== currentItems?.length || selectedItems?.length === 0) {
const newSelectedItems = [...currentItems].map((template) => template.id);
setSelectedItems(newSelectedItems);
} else {
setSelectedItems([]);
}
}
const toggleItem = (id) => {
if (selectedItems.includes(id)) {
const newSelectedItems = [...selectedItems].filter((item) => item !== id);
setSelectedItems(newSelectedItems);
} else {
const newSelectedItems = [...selectedItems, id];
setSelectedItems(newSelectedItems);
}
}
const checkSelectAll = () => {
return selectedItems?.length === currentItems?.length && selectedItems?.length > 0;
}
const table = (
<div className="list row mb-4">
<div className="col-md-12">
<table className="personnel-info-table">
<thead>
<tr>
<th className="th-checkbox"><input type="checkbox" checked={checkSelectAll()} onClick={() => toggleSelectedAllItems()}></input></th>
<th className="th-index">No.</th>
{
columns.filter(col => col.show).map((column, index) => <th className="sortable-header" key={index}>
{column.label} <span className="float-right" onClick={() => sortTableWithField(column.key)}><img src={`/images/${getSortingImg(column.key)}.png`} alt="sort"></img></span>
</th>)
}
<th>Actions</th>
</tr>
</thead>
<tbody>
{
currentItems?.map((template, index) => <tr key={template?.id}>
<td className="td-checkbox"><input type="checkbox" checked={selectedItems.includes(template.id)} onClick={()=>toggleItem(template?.id)}/></td>
<td className="td-index">{index + 1 + itemOffset}</td>
{columns.find(col => col.key === 'name')?.show && <td>
<button className="btn btn-link btn-sm" onClick={() => goToView(template?.id)}>{template?.name}</button>
</td>}
{columns.find(col => col.key === 'template_date')?.show && <td>{template?.template_date}</td>}
{columns.find(col => col.key === 'routes_count')?.show && <td>{template?.routes_count}</td>}
{columns.find(col => col.key === 'create_by')?.show && <td>{template?.create_by}</td>}
{columns.find(col => col.key === 'create_date')?.show && <td>{template?.create_date}</td>}
<td>
<Eye size={16} className="clickable me-2" onClick={() => goToView(template?.id)} title="View" />
</td>
</tr>)
}
</tbody>
</table>
<div className="pagination-container">
<ReactPaginate
key={itemsPerPage}
className="customers-pagination"
breakLabel="..."
nextLabel="Next"
onPageChange={handlePageClick}
pageRangeDisplayed={5}
pageCount={pageCount}
previousLabel="Prev"
renderOnZeroPageCount={null}
containerClassName="pagination justify-content-center"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-prev-item"
previousLinkClassName="page-link"
nextClassName="page-next-item"
nextLinkClassName="page-link"
activeClassName="active"
breakClassName="page-item"
breakLinkClassName="page-link"
/>
<div className="select-page-container">
<input type="number" className="page-picker" max={pageCount} min={1} value={currentPage} onChange={(e) => {handlePageSelect(e.target.value); setCurrentPage(e.target.value)}}/>
<div className="per-page-label"> {` of ${pageCount}`}</div>
</div>
<div className="select-page-container">
<select className="per-page" value={itemsPerPage} onChange={e => setItemsPerPage(e.target.value)}>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select> <span className="per-page-label"> /page</span>
</div>
</div>
</div>
</div>
);
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
<Breadcrumb.Item active>
Daily Route Templates
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
All Daily Route Templates
</h4>
</div>
</div>
<div className="app-main-content-list-container">
<div className="app-main-content-list-func-container">
<Tabs defaultActiveKey="dailyTemplates" id="daily-templates-tab">
<Tab eventKey="dailyTemplates" title="Daily Route Templates">
{table}
</Tab>
</Tabs>
<div className="list-func-panel">
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
</div>
</div>
</div>
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>
<Modal.Header closeButton>
<Modal.Title>Delete Template</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the template "{templateToDelete?.name}"?
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => setShowDeleteModal(false)}>
Cancel
</Button>
<Button variant="danger" onClick={deleteTemplate}>
Delete
</Button>
</Modal.Footer>
</Modal>
</>
)
};
export default DailyTemplatesList;

View File

@ -0,0 +1,148 @@
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { selectAllRoutes, selectAllBreakfastRecords, selectAllLunchRecords, selectAllSnackRecords, transRoutesSlice } from "./../../store";
import { TransRoutesService } from "../../services";
import BreakfastSection from './BreakfastSection';
import LunchSection from "./LunchSection";
import SnackSection from "./SnackSection";
import { Breadcrumb } from "react-bootstrap";
import moment from 'moment';
const MealStatus = () => {
const dispatch = useDispatch();
const { fetchAllRoutes, fetchAllBreakfastRecords, fetchAllLunchRecords, fetchAllSnackRecords } = transRoutesSlice.actions;
const allRoutes = useSelector(selectAllRoutes);
const breakfastRecords = useSelector(selectAllBreakfastRecords);
const lunchRecords = useSelector(selectAllLunchRecords);
const snackRecords = useSelector(selectAllSnackRecords);
useEffect(() => {
dispatch(fetchAllRoutes());
dispatch(fetchAllBreakfastRecords());
dispatch(fetchAllLunchRecords());
dispatch(fetchAllSnackRecords());
}, []);
const confimHasBreakfast = (customer) => {
TransRoutesService.createBreakfastRecords({
customer_id: customer?.customer_id,
customer_name: customer?.customer_name,
has_breakfast: true,
date: moment(new Date()).format('MM/DD/YYYY'),
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
date: new Date()
}]
}).then(() => {
dispatch(fetchAllBreakfastRecords());
})
}
const confirmHasLunch = (customer) => {
TransRoutesService.createLunchRecords({
customer_id: customer?.customer_id,
customer_name: customer?.customer_name,
has_lunch: true,
date: moment(new Date()).format('MM/DD/YYYY'),
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
date: new Date()
}]
}).then(() => {
dispatch(fetchAllLunchRecords());
})
}
const confirmHasSnack = (customer) => {
TransRoutesService.createSnackRecords({
customer_id: customer?.customer_id,
customer_name: customer?.customer_name,
has_snack: true,
date: moment(new Date()).format('MM/DD/YYYY'),
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
create_date: new Date(),
edit_history: [{
employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
date: new Date()
}]
}).then(() => {
dispatch(fetchAllSnackRecords());
})
}
const removeBreakfastRecord = (customer_id) => {
const breakfast = breakfastRecords.find(b => b.customer_id === customer_id)?.id;
TransRoutesService.deleteBreakfastRecords(breakfast).then(() => {
dispatch(fetchAllBreakfastRecords());
})
}
const removeLunchRecord = (customer_id) => {
const lunch = lunchRecords.find(b => b.customer_id === customer_id)?.id;
TransRoutesService.deleteLunchRecords(lunch).then(() => {
dispatch(fetchAllLunchRecords());
})
}
const removeSnackRecord = (customer_id) => {
const snack = snackRecords.find(b => b.customer_id === customer_id)?.id;
TransRoutesService.deleteSnackRecords(snack).then(() => {
dispatch(fetchAllSnackRecords());
})
}
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/">Lobby</Breadcrumb.Item>
<Breadcrumb.Item active>
Meal Status
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>Meal Status</h4>
</div>
</div>
<div className="app-main-content-list-container">
<div className="list row">
<div className="col-md-4 col-sm-12 mb-4">
<BreakfastSection
transRoutes={allRoutes}
breakfastRecords={breakfastRecords}
confimHasBreakfast={confimHasBreakfast}
removeBreakfastRecord={removeBreakfastRecord}
sectionName={'Breakfast Info'}
/>
</div>
<div className="col-md-4 col-sm-12 mb-4">
<LunchSection
transRoutes={allRoutes}
lunchRecords={lunchRecords}
confirmHasLunch={confirmHasLunch}
removeLunchRecord={removeLunchRecord}
sectionName={'Lunch Info'}
/>
</div>
<div className="col-md-4 col-sm-12 mb-4">
<SnackSection
transRoutes={allRoutes}
snackRecords={snackRecords}
confirmHasSnack={confirmHasSnack}
removeSnackRecord={removeSnackRecord}
sectionName={'Snack Info'}
/>
</div>
</div>
</div>
</>
);
};
export default MealStatus;

View File

@ -0,0 +1,263 @@
import React, {useEffect, useState} from "react";
import { useSelector } from "react-redux";
import { useParams, useNavigate } from "react-router-dom";
import { DailyRoutesTemplateService } from "../../services";
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
import { Breadcrumb, Tabs, Tab, Spinner, Button } from "react-bootstrap";
import RouteCustomerEditor from "./RouteCustomerEditor";
import { Trash } from "react-bootstrap-icons";
const UpdateDailyTemplateRoute = () => {
const params = useParams();
const navigate = useNavigate();
const drivers = useSelector(selectAllActiveDrivers);
const vehicles = useSelector(selectAllActiveVehicles);
const [template, setTemplate] = useState(null);
const [currentRoute, setCurrentRoute] = useState(null);
const [routeName, setRouteName] = useState('');
const [newDriver, setNewDriver] = useState('');
const [newVehicle, setNewVehicle] = useState('');
const [newRouteType, setNewRouteType] = useState('');
const [newCustomerList, setNewCustomerList] = useState([]);
const [errorMessage, setErrorMessage] = useState(undefined);
const [successMessage, setSuccessMessage] = useState(undefined);
const [saving, setSaving] = useState(false);
useEffect(() => {
fetchTemplateAndRoute();
}, [params.id, params.routeId]);
const fetchTemplateAndRoute = () => {
DailyRoutesTemplateService.getDailyRoutesTemplate(params.id).then(data => {
setTemplate(data.data);
const route = data.data.routes?.find(r => r._id === params.routeId);
setCurrentRoute(route);
setRouteName(route.name);
setNewDriver(route.driver);
setNewVehicle(route.vehicle);
setNewRouteType(route.type);
setNewCustomerList(route.route_customer_list || []);
});
};
const goBack = () => {
navigate(`/trans-routes/daily-templates/${params.id}/view-route/${params.routeId}`);
};
const updateCurrentRoute = () => {
try {
if (!routeName || routeName === '') {
setErrorMessage('Route Name is Required');
setTimeout(() => setErrorMessage(undefined), 5000);
return;
}
if (!newRouteType || newRouteType === '') {
setErrorMessage('Route Type is Required');
setTimeout(() => setErrorMessage(undefined), 5000);
return;
}
setSaving(true);
// Update the route in the template
const updatedRoutes = template.routes.map(route => {
if (route._id === params.routeId) {
return {
...route,
name: routeName,
driver: newDriver,
vehicle: newVehicle,
type: newRouteType,
route_customer_list: newCustomerList
};
}
return route;
});
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
...template,
routes: updatedRoutes
}).then(() => {
setSaving(false);
setSuccessMessage('Route updated successfully!');
setTimeout(() => {
goBack();
}, 1500);
}).catch(err => {
setSaving(false);
setErrorMessage('Failed to update route');
setTimeout(() => setErrorMessage(undefined), 5000);
});
} catch(ex) {
setSaving(false);
setErrorMessage('An error occurred');
setTimeout(() => setErrorMessage(undefined), 5000);
}
};
const deleteRoute = () => {
if (window.confirm(`Are you sure you want to delete the route "${currentRoute.name}" from this template?`)) {
const updatedRoutes = template.routes.filter(r => r._id !== params.routeId);
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
...template,
routes: updatedRoutes
}).then(() => {
navigate(`/trans-routes/daily-templates/view/${params.id}`);
}).catch(err => {
alert('Failed to delete route');
});
}
};
if (!template || !currentRoute) {
return (
<div className="list row mb-4">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>
);
}
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
Daily Route Templates
</Breadcrumb.Item>
<Breadcrumb.Item href={`/trans-routes/daily-templates/view/${params.id}`}>
{template.name}
</Breadcrumb.Item>
<Breadcrumb.Item active>
Update Route: {currentRoute.name}
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
Update Route in Template
<button className="btn btn-link btn-sm" onClick={goBack}>Cancel</button>
</h4>
</div>
</div>
{errorMessage && (
<div className="alert alert-danger alert-dismissible fade show" role="alert">
{errorMessage}
<button onClick={() => setErrorMessage(undefined)} type="button" className="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
)}
{successMessage && (
<div className="alert alert-success alert-dismissible fade show" role="alert">
{successMessage}
<button onClick={() => setSuccessMessage(undefined)} type="button" className="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
)}
<div className="app-main-content-list-container">
<Tabs defaultActiveKey="basicInfo" id="route-edit-tab">
<Tab eventKey="basicInfo" title="Basic Information">
<div className="app-main-content-fields-section">
<div className="mb-3 me-2">
<label className="form-label">Route Name *</label>
<input
type="text"
className="form-control"
value={routeName}
onChange={(e) => setRouteName(e.target.value)}
disabled={saving}
/>
</div>
<div className="mb-3">
<label className="form-label">Route Type *</label>
<select
className="form-select"
value={newRouteType}
onChange={(e) => setNewRouteType(e.target.value)}
disabled={saving}
>
<option value="">Select Type</option>
<option value="inbound">Inbound</option>
<option value="outbound">Outbound</option>
</select>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="mb-3 me-2">
<label className="form-label">Driver</label>
<select
className="form-select"
value={newDriver}
onChange={(e) => setNewDriver(e.target.value)}
disabled={saving}
>
<option value="">Select Driver</option>
{drivers.map((driver) => (
<option key={driver.id} value={driver.id}>{driver.name}</option>
))}
</select>
</div>
<div className="mb-3">
<label className="form-label">Vehicle</label>
<select
className="form-select"
value={newVehicle}
onChange={(e) => setNewVehicle(e.target.value)}
disabled={saving}
>
<option value="">Select Vehicle</option>
{vehicles.map((vehicle) => (
<option key={vehicle.id} value={vehicle.id}>{vehicle.vehicle_number}</option>
))}
</select>
</div>
</div>
<div className="app-main-content-fields-section">
<div className="mt-4">
<button
className="btn btn-primary me-2"
onClick={updateCurrentRoute}
disabled={saving}
>
{saving ? <><Spinner size="sm" className="me-2" />Saving...</> : 'Save Changes'}
</button>
<button
className="btn btn-secondary me-2"
onClick={goBack}
disabled={saving}
>
Cancel
</button>
<button
className="btn btn-danger"
onClick={deleteRoute}
disabled={saving}
>
<Trash size={16} className="me-2" />
Delete Route
</button>
</div>
</div>
</Tab>
<Tab eventKey="customers" title="Customers">
<div className="app-main-content-fields-section">
<RouteCustomerEditor
currentRoute={currentRoute}
setNewCustomerList={setNewCustomerList}
/>
</div>
</Tab>
</Tabs>
</div>
</>
);
};
export default UpdateDailyTemplateRoute;

View File

@ -0,0 +1,223 @@
import React, {useState, useEffect} from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { DailyRoutesTemplateService } from "../../services";
import { Breadcrumb, Modal, Button, Spinner } from "react-bootstrap";
import { PencilSquare, Trash } from "react-bootstrap-icons";
import RoutesSection from "./RoutesSection";
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
const ViewDailyTemplate = () => {
const params = useParams();
const navigate = useNavigate();
const drivers = useSelector(selectAllActiveDrivers);
const vehicles = useSelector(selectAllActiveVehicles);
const [template, setTemplate] = useState(null);
const [inboundRoutes, setInboundRoutes] = useState([]);
const [outboundRoutes, setOutboundRoutes] = useState([]);
const [showEditNameModal, setShowEditNameModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [newName, setNewName] = useState('');
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
useEffect(() => {
fetchTemplate();
}, [params.id]);
const fetchTemplate = () => {
DailyRoutesTemplateService.getDailyRoutesTemplate(params.id).then(data => {
setTemplate(data.data);
setNewName(data.data.name);
// Separate routes by type
const inbound = data.data.routes?.filter(route => route.type === 'inbound') || [];
const outbound = data.data.routes?.filter(route => route.type === 'outbound') || [];
setInboundRoutes(inbound);
setOutboundRoutes(outbound);
});
};
const goBack = () => {
navigate('/trans-routes/daily-templates/list');
};
const openEditNameModal = () => {
setShowEditNameModal(true);
};
const closeEditNameModal = () => {
setShowEditNameModal(false);
setNewName(template.name);
};
const saveName = () => {
if (!newName || newName.trim() === '') {
alert('Please enter a template name');
return;
}
setSaving(true);
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
...template,
name: newName
}).then(() => {
setSaving(false);
setShowEditNameModal(false);
fetchTemplate();
}).catch(err => {
setSaving(false);
alert('Failed to update template name');
});
};
const handleRouteClick = (routeId) => {
navigate(`/trans-routes/daily-templates/${params.id}/view-route/${routeId}`);
};
const confirmDelete = () => {
setShowDeleteModal(true);
};
const deleteTemplate = () => {
setDeleting(true);
DailyRoutesTemplateService.deleteDailyRoutesTemplate(params.id).then(() => {
setDeleting(false);
setShowDeleteModal(false);
navigate('/trans-routes/daily-templates/list');
}).catch(err => {
setDeleting(false);
alert('Failed to delete template');
});
};
const goToCreateRoute = (type) => {
navigate(`/trans-routes/daily-templates/${params.id}/create-route?type=${type}`);
};
if (!template) {
return (
<div className="list row mb-4">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>
);
}
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
Daily Route Templates
</Breadcrumb.Item>
<Breadcrumb.Item active>
{template.name}
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
{template.name}
<PencilSquare
size={20}
className="clickable ms-2"
onClick={openEditNameModal}
title="Edit template name"
/>
<button className="btn btn-link btn-sm" onClick={goBack}>Back to List</button>
<button className="btn btn-danger btn-sm ms-2" onClick={confirmDelete}>
<Trash size={14} className="me-1" />Delete Template
</button>
</h4>
<div className="text-muted">
<small>Template Date: {template.template_date} | Created by: {template.create_by}</small>
</div>
</div>
</div>
<div className="app-main-content-list-container">
<div className="list row">
<div className="col-md-12 mb-4">
<RoutesSection
transRoutes={inboundRoutes.map(route => ({...route, id: route._id}))}
sectionName="Inbound Routes"
drivers={drivers}
vehicles={vehicles}
onRouteClick={handleRouteClick}
isTemplate={true}
templateId={params.id}
canAddNew={true}
addText="+Add Route"
redirect={goToCreateRoute}
routeType="inbound"
/>
</div>
<div className="col-md-12 mb-4">
<RoutesSection
transRoutes={outboundRoutes.map(route => ({...route, id: route._id}))}
sectionName="Outbound Routes"
drivers={drivers}
vehicles={vehicles}
onRouteClick={handleRouteClick}
isTemplate={true}
templateId={params.id}
canAddNew={true}
addText="+Add Route"
redirect={goToCreateRoute}
routeType="outbound"
/>
</div>
</div>
</div>
<Modal show={showEditNameModal} onHide={closeEditNameModal}>
<Modal.Header closeButton>
<Modal.Title>Edit Template Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="mb-3">
<label className="form-label">Template Name</label>
<input
type="text"
className="form-control"
value={newName}
onChange={(e) => setNewName(e.target.value)}
disabled={saving}
/>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeEditNameModal} disabled={saving}>
Cancel
</Button>
<Button variant="primary" onClick={saveName} disabled={saving}>
{saving ? <><Spinner size="sm" className="me-2" />Saving...</> : 'Save'}
</Button>
</Modal.Footer>
</Modal>
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>
<Modal.Header closeButton>
<Modal.Title>Delete Template</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the template "{template?.name}"? This action cannot be undone.
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => setShowDeleteModal(false)} disabled={deleting}>
Cancel
</Button>
<Button variant="danger" onClick={deleteTemplate} disabled={deleting}>
{deleting ? <><Spinner size="sm" className="me-2" />Deleting...</> : 'Delete'}
</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default ViewDailyTemplate;

View File

@ -0,0 +1,175 @@
import React, {useState, useEffect} from "react";
import { useSelector } from "react-redux";
import { useParams, useNavigate } from "react-router-dom";
import { DailyRoutesTemplateService } from "../../services";
import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store";
import { Breadcrumb, Spinner, Modal, Button } from "react-bootstrap";
import { Pencil, Trash } from "react-bootstrap-icons";
import RouteCustomerTable from "./RouteCustomerTable";
const ViewDailyTemplateRoute = () => {
const params = useParams();
const navigate = useNavigate();
const drivers = useSelector(selectAllActiveDrivers);
const vehicles = useSelector(selectAllActiveVehicles);
const [template, setTemplate] = useState(null);
const [currentRoute, setCurrentRoute] = useState(null);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [deleting, setDeleting] = useState(false);
const currentVehicle = vehicles.find(item => item.id === currentRoute?.vehicle);
const currentDriver = drivers.find(item => item.id === currentRoute?.driver);
useEffect(() => {
fetchTemplateAndRoute();
}, [params.id, params.routeId]);
const fetchTemplateAndRoute = () => {
DailyRoutesTemplateService.getDailyRoutesTemplate(params.id).then(data => {
setTemplate(data.data);
const route = data.data.routes?.find(r => r._id === params.routeId);
setCurrentRoute(route);
});
};
const goBack = () => {
navigate(`/trans-routes/daily-templates/view/${params.id}`);
};
const goToEdit = () => {
navigate(`/trans-routes/daily-templates/${params.id}/update-route/${params.routeId}`);
};
const confirmDelete = () => {
setShowDeleteModal(true);
};
const deleteRoute = () => {
setDeleting(true);
const updatedRoutes = template.routes.filter(r => r._id !== params.routeId);
DailyRoutesTemplateService.updateDailyRoutesTemplate(params.id, {
...template,
routes: updatedRoutes
}).then(() => {
setDeleting(false);
setShowDeleteModal(false);
navigate(`/trans-routes/daily-templates/view/${params.id}`);
}).catch(err => {
setDeleting(false);
alert('Failed to delete route');
});
};
if (!template || !currentRoute) {
return (
<div className="list row mb-4">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>
);
}
return (
<>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
<Breadcrumb.Item href="/trans-routes/daily-templates/list">
Daily Route Templates
</Breadcrumb.Item>
<Breadcrumb.Item href={`/trans-routes/daily-templates/view/${params.id}`}>
{template.name}
</Breadcrumb.Item>
<Breadcrumb.Item active>
{currentRoute.name}
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>
View Route in Template: {currentRoute.name}
<button className="btn btn-link btn-sm" onClick={goBack}>Back</button>
</h4>
</div>
</div>
<div className="app-main-content-list-container">
<div className="app-main-content-fields-section">
<div className="row">
<div className="col-md-6">
<div className="mb-3">
<strong>Route Name:</strong> {currentRoute.name}
</div>
<div className="mb-3">
<strong>Type:</strong> {currentRoute.type}
</div>
<div className="mb-3">
<strong>Vehicle:</strong> {currentVehicle?.vehicle_number || 'Not assigned'}
</div>
<div className="mb-3">
<strong>Driver:</strong> {currentDriver?.name || 'Not assigned'}
</div>
</div>
<div className="col-md-6">
<div className="mb-3">
<strong>Start Mileage:</strong> {currentRoute.start_mileage || 'N/A'}
</div>
<div className="mb-3">
<strong>End Mileage:</strong> {currentRoute.end_mileage || 'N/A'}
</div>
<div className="mb-3">
<strong>Customers:</strong> {currentRoute.route_customer_list?.length || 0}
</div>
</div>
</div>
<div className="mt-4">
<button className="btn btn-primary me-2" onClick={goToEdit}>
<Pencil size={16} className="me-2" />
Update Route
</button>
<button className="btn btn-danger" onClick={confirmDelete}>
<Trash size={16} className="me-2" />
Delete Route
</button>
</div>
</div>
{currentRoute.route_customer_list && currentRoute.route_customer_list.length > 0 && (
<div className="list row mt-4">
<div className="col-md-12">
<h5>Customer List</h5>
<RouteCustomerTable
transRoutes={[currentRoute]}
sectionName={'Customers'}
vehicles={[]}
isTemplate={true}
/>
</div>
</div>
)}
</div>
<Modal show={showDeleteModal} onHide={() => setShowDeleteModal(false)}>
<Modal.Header closeButton>
<Modal.Title>Delete Route</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete the route "{currentRoute.name}" from this template?
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => setShowDeleteModal(false)} disabled={deleting}>
Cancel
</Button>
<Button variant="danger" onClick={deleteRoute} disabled={deleting}>
{deleting ? <><Spinner size="sm" className="me-2" />Deleting...</> : 'Delete'}
</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default ViewDailyTemplateRoute;

View File

@ -0,0 +1,34 @@
import http from "../http-common";
const getAll = (template_date) => {
const params = {};
if (template_date) {
params.template_date = template_date;
}
return http.get("/daily-routes-templates", {params});
};
const createNewDailyRoutesTemplate = (data) => {
return http.post('/daily-routes-templates', data);
};
const updateDailyRoutesTemplate = (id, data) => {
return http.put(`/daily-routes-templates/${id}`, data);
};
const getDailyRoutesTemplate = (id) => {
return http.get(`/daily-routes-templates/${id}`);
};
const deleteDailyRoutesTemplate = (id) => {
return http.delete(`/daily-routes-templates/${id}`);
};
export const DailyRoutesTemplateService = {
getAll,
createNewDailyRoutesTemplate,
updateDailyRoutesTemplate,
getDailyRoutesTemplate,
deleteDailyRoutesTemplate
};

View File

@ -0,0 +1,95 @@
// Resource Type Options
export const RESOURCE_TYPE_OPTIONS = [
{ value: 'doctor', label: 'Doctor' },
{ value: 'pharmacy', label: 'Pharmacy' },
{ value: 'hospital', label: 'Hospital' },
{ value: 'surgical center', label: 'Surgical Center' },
{ value: 'government agency', label: 'Government Agency' },
{ value: 'other', label: 'Other' },
];
export const RESOURCE_TYPE_TEXT = {
'doctor': 'Doctor',
'pharmacy': 'Pharmacy',
'hospital': 'Hospital',
'surgical center': 'Surgical Center',
'government agency': 'Government Agency',
'other': 'Other',
};
// Resource Specialty Options
export const RESOURCE_SPECIALTY_OPTIONS = [
{ value: 'Family Medicine (PCP)', label: 'Family Medicine (PCP)' },
{ value: 'Acupuncture', label: 'Acupuncture' },
{ value: 'Allergy & Asthma', label: 'Allergy & Asthma' },
{ value: 'Audiology', label: 'Audiology' },
{ value: 'Behavior Health/Social Worker', label: 'Behavior Health/Social Worker' },
{ value: 'Botox Therapy', label: 'Botox Therapy' },
{ value: 'Breast Surgery', label: 'Breast Surgery' },
{ value: 'Cardiology', label: 'Cardiology' },
{ value: 'Cardiovascular', label: 'Cardiovascular' },
{ value: 'Colon & Rectal Surgery', label: 'Colon & Rectal Surgery' },
{ value: 'Dentist', label: 'Dentist' },
{ value: 'Dermatology', label: 'Dermatology' },
{ value: 'Dialysis', label: 'Dialysis' },
{ value: 'Endocrinology & Diabetes', label: 'Endocrinology & Diabetes' },
{ value: 'Endodontist', label: 'Endodontist' },
{ value: 'Endoscopy Center', label: 'Endoscopy Center' },
{ value: 'Otolaryngology (ENT)', label: 'Otolaryngology (ENT)' },
{ value: 'Eye Surgery Center', label: 'Eye Surgery Center' },
{ value: 'Gastroenterology', label: 'Gastroenterology' },
{ value: 'General Surgery', label: 'General Surgery' },
{ value: 'GYN Oncology', label: 'GYN Oncology' },
{ value: 'Head & Neck Surgery', label: 'Head & Neck Surgery' },
{ value: 'Health Boutique', label: 'Health Boutique' },
{ value: 'Hearing Aids', label: 'Hearing Aids' },
{ value: 'Hematology & Oncology', label: 'Hematology & Oncology' },
{ value: 'Hepatology', label: 'Hepatology' },
{ value: 'Hospital', label: 'Hospital' },
{ value: 'Infectious disease', label: 'Infectious disease' },
{ value: 'Medical Center', label: 'Medical Center' },
{ value: 'Lab', label: 'Lab' },
{ value: 'Modified Barium Swallow (MBS) Study', label: 'Modified Barium Swallow (MBS) Study' },
{ value: 'Medical Supply', label: 'Medical Supply' },
{ value: 'Nephrology', label: 'Nephrology' },
{ value: 'Neuro Surgeon', label: 'Neuro Surgeon' },
{ value: 'Neurology', label: 'Neurology' },
{ value: 'OB/GYN', label: 'OB/GYN' },
{ value: 'Optometry (OD)', label: 'Optometry (OD)' },
{ value: 'Oncology', label: 'Oncology' },
{ value: 'Oncology Center', label: 'Oncology Center' },
{ value: 'Ophthalmology', label: 'Ophthalmology' },
{ value: 'Ophthalmology (Retina Specialist)', label: 'Ophthalmology (Retina Specialist)' },
{ value: 'Oral Surgery', label: 'Oral Surgery' },
{ value: 'Orthopaedic', label: 'Orthopaedic' },
{ value: 'Osteopath', label: 'Osteopath' },
{ value: 'Pain Management', label: 'Pain Management' },
{ value: 'Periodontist', label: 'Periodontist' },
{ value: 'Pharmacy', label: 'Pharmacy' },
{ value: 'Physical Therapy', label: 'Physical Therapy' },
{ value: 'Physical, Occupational, & Speech Therapy', label: 'Physical, Occupational, & Speech Therapy' },
{ value: 'Podiatry', label: 'Podiatry' },
{ value: 'Psychiatry', label: 'Psychiatry' },
{ value: 'Pulmonology', label: 'Pulmonology' },
{ value: 'Radiation Oncology', label: 'Radiation Oncology' },
{ value: 'Radiology', label: 'Radiology' },
{ value: 'Rehabilitation', label: 'Rehabilitation' },
{ value: 'Rheumatology', label: 'Rheumatology' },
{ value: 'Sleep Medicine', label: 'Sleep Medicine' },
{ value: 'Substance Abuse Treatment', label: 'Substance Abuse Treatment' },
{ value: 'Sports Medicine', label: 'Sports Medicine' },
{ value: 'Surgery', label: 'Surgery' },
{ value: 'Surgery Center', label: 'Surgery Center' },
{ value: 'Thoracic Surgery', label: 'Thoracic Surgery' },
{ value: 'Traditional Chinese Medicine', label: 'Traditional Chinese Medicine' },
{ value: 'Urgent Care', label: 'Urgent Care' },
{ value: 'Urogynecology', label: 'Urogynecology' },
{ value: 'Urology', label: 'Urology' },
{ value: 'Vascular and Vein', label: 'Vascular and Vein' },
{ value: 'Vascular & Interventional Radiologist', label: 'Vascular & Interventional Radiologist' },
{ value: 'Weight Loss / GYM', label: 'Weight Loss / GYM' },
{ value: 'Wound Clinic', label: 'Wound Clinic' },
];
// Legacy list format for backward compatibility
export const RESOURCE_SPECIALTY_LIST = RESOURCE_SPECIALTY_OPTIONS.map(opt => opt.value);

View File

@ -0,0 +1,155 @@
// Vehicle Seating Capacity Options
export const SEATING_CAPACITY_OPTIONS = [
{ value: '2', label: '2' },
{ value: '3', label: '3' },
{ value: '4', label: '4' },
{ value: '5', label: '5' },
{ value: '6', label: '6' },
{ value: '10', label: '10' },
{ value: '11', label: '11' },
{ value: '12', label: '12' },
{ value: '13', label: '13' },
{ value: '14', label: '14' },
{ value: '15', label: '15' },
{ value: '23', label: '23' }
];
// Fuel Type Options
export const FUEL_TYPE = {
PETROL: 'petrol',
DIESEL: 'diesel'
};
export const FUEL_TYPE_TEXT = {
[FUEL_TYPE.PETROL]: 'Petrol',
[FUEL_TYPE.DIESEL]: 'Diesel'
};
// Title/Ownership Options
export const VEHICLE_TITLE = {
WORLDSHINE_CARE: 'worldshineCare',
WORLDSHINE_INTERNATIONAL: 'worldshineInternational',
WORLDSHINE_CLOVERLEAF: 'worldshineCloverleaf',
OTHER: 'other'
};
export const VEHICLE_TITLE_TEXT = {
[VEHICLE_TITLE.WORLDSHINE_CARE]: 'Worldshine Care LLC',
[VEHICLE_TITLE.WORLDSHINE_INTERNATIONAL]: 'Worldshine International LLC',
[VEHICLE_TITLE.WORLDSHINE_CLOVERLEAF]: 'Worldshine Cloverleaf LLC',
[VEHICLE_TITLE.OTHER]: 'Other-Please Specify'
};
// Lift Equipped Options
export const LIFT_EQUIPPED = {
YES: 'true',
NO: 'false'
};
export const LIFT_EQUIPPED_TEXT = {
[LIFT_EQUIPPED.YES]: 'Yes',
[LIFT_EQUIPPED.NO]: 'No'
};
// Repair Part Name Options (common vehicle parts)
export const REPAIR_PART_NAME = {
BRAKE_PADS: 'brakePads',
BRAKE_ROTORS: 'brakeRotors',
OIL_FILTER: 'oilFilter',
AIR_FILTER: 'airFilter',
CABIN_FILTER: 'cabinFilter',
SPARK_PLUGS: 'sparkPlugs',
BATTERY: 'battery',
ALTERNATOR: 'alternator',
STARTER_MOTOR: 'starterMotor',
TIRES: 'tires',
WHEEL_ALIGNMENT: 'wheelAlignment',
SUSPENSION: 'suspension',
SHOCKS: 'shocks',
STRUTS: 'struts',
TRANSMISSION_FLUID: 'transmissionFluid',
COOLANT: 'coolant',
POWER_STEERING_FLUID: 'powerSteeringFluid',
WINDSHIELD_WIPERS: 'windshieldWipers',
HEADLIGHTS: 'headlights',
TAIL_LIGHTS: 'tailLights',
MIRRORS: 'mirrors',
BELTS: 'belts',
HOSES: 'hoses',
EXHAUST_SYSTEM: 'exhaustSystem',
MUFFLER: 'muffler',
CATALYTIC_CONVERTER: 'catalyticConverter',
FUEL_PUMP: 'fuelPump',
FUEL_FILTER: 'fuelFilter',
WATER_PUMP: 'waterPump',
THERMOSTAT: 'thermostat',
RADIATOR: 'radiator',
AC_COMPRESSOR: 'acCompressor',
HEATER_CORE: 'heaterCore',
CLUTCH: 'clutch',
CV_JOINTS: 'cvJoints',
WHEEL_BEARINGS: 'wheelBearings',
TIE_RODS: 'tieRods',
BALL_JOINTS: 'ballJoints',
CONTROL_ARMS: 'controlArms',
SWAY_BAR: 'swayBar',
OTHER: 'other'
};
export const REPAIR_PART_NAME_TEXT = {
[REPAIR_PART_NAME.BRAKE_PADS]: 'Brake Pads',
[REPAIR_PART_NAME.BRAKE_ROTORS]: 'Brake Rotors',
[REPAIR_PART_NAME.OIL_FILTER]: 'Oil Filter',
[REPAIR_PART_NAME.AIR_FILTER]: 'Air Filter',
[REPAIR_PART_NAME.CABIN_FILTER]: 'Cabin Filter',
[REPAIR_PART_NAME.SPARK_PLUGS]: 'Spark Plugs',
[REPAIR_PART_NAME.BATTERY]: 'Battery',
[REPAIR_PART_NAME.ALTERNATOR]: 'Alternator',
[REPAIR_PART_NAME.STARTER_MOTOR]: 'Starter Motor',
[REPAIR_PART_NAME.TIRES]: 'Tires',
[REPAIR_PART_NAME.WHEEL_ALIGNMENT]: 'Wheel Alignment',
[REPAIR_PART_NAME.SUSPENSION]: 'Suspension',
[REPAIR_PART_NAME.SHOCKS]: 'Shocks',
[REPAIR_PART_NAME.STRUTS]: 'Struts',
[REPAIR_PART_NAME.TRANSMISSION_FLUID]: 'Transmission Fluid',
[REPAIR_PART_NAME.COOLANT]: 'Coolant',
[REPAIR_PART_NAME.POWER_STEERING_FLUID]: 'Power Steering Fluid',
[REPAIR_PART_NAME.WINDSHIELD_WIPERS]: 'Windshield Wipers',
[REPAIR_PART_NAME.HEADLIGHTS]: 'Headlights',
[REPAIR_PART_NAME.TAIL_LIGHTS]: 'Tail Lights',
[REPAIR_PART_NAME.MIRRORS]: 'Mirrors',
[REPAIR_PART_NAME.BELTS]: 'Belts',
[REPAIR_PART_NAME.HOSES]: 'Hoses',
[REPAIR_PART_NAME.EXHAUST_SYSTEM]: 'Exhaust System',
[REPAIR_PART_NAME.MUFFLER]: 'Muffler',
[REPAIR_PART_NAME.CATALYTIC_CONVERTER]: 'Catalytic Converter',
[REPAIR_PART_NAME.FUEL_PUMP]: 'Fuel Pump',
[REPAIR_PART_NAME.FUEL_FILTER]: 'Fuel Filter',
[REPAIR_PART_NAME.WATER_PUMP]: 'Water Pump',
[REPAIR_PART_NAME.THERMOSTAT]: 'Thermostat',
[REPAIR_PART_NAME.RADIATOR]: 'Radiator',
[REPAIR_PART_NAME.AC_COMPRESSOR]: 'A/C Compressor',
[REPAIR_PART_NAME.HEATER_CORE]: 'Heater Core',
[REPAIR_PART_NAME.CLUTCH]: 'Clutch',
[REPAIR_PART_NAME.CV_JOINTS]: 'CV Joints',
[REPAIR_PART_NAME.WHEEL_BEARINGS]: 'Wheel Bearings',
[REPAIR_PART_NAME.TIE_RODS]: 'Tie Rods',
[REPAIR_PART_NAME.BALL_JOINTS]: 'Ball Joints',
[REPAIR_PART_NAME.CONTROL_ARMS]: 'Control Arms',
[REPAIR_PART_NAME.SWAY_BAR]: 'Sway Bar',
[REPAIR_PART_NAME.OTHER]: 'Other'
};
// Quantity Options (1-10)
export const QUANTITY_OPTIONS = [
{ value: '1', label: '1' },
{ value: '2', label: '2' },
{ value: '3', label: '3' },
{ value: '4', label: '4' },
{ value: '5', label: '5' },
{ value: '6', label: '6' },
{ value: '7', label: '7' },
{ value: '8', label: '8' },
{ value: '9', label: '9' },
{ value: '10', label: '10' }
];